├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .idea ├── .name ├── DevMod.iml ├── codeStyles │ └── Project.xml ├── encodings.xml ├── jsLibraryMappings.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE.md ├── README.md ├── devmod.js ├── env.json ├── index.js ├── migrations └── 001_create_schema.sql ├── package.json └── src ├── activities.js ├── approvedRoles.js ├── colours.js ├── commands ├── about.js ├── ban.js ├── clearWarns.js ├── gbp.js ├── help.js ├── lmgtfy.js ├── mute.js ├── ping.js ├── prune.js ├── report.js ├── role.js ├── roles.js ├── stats.js ├── tag.js ├── tags.js ├── unban.js ├── unmute.js ├── users.js ├── warn.js └── warnList.js ├── common.js ├── config.js ├── reactionRoles.js ├── sendRolesMessage.js ├── tags.js └── utils ├── index.js └── migrate.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "current" 8 | } 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'standard' 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn.lock 3 | dist 4 | .cache 5 | package-lock.json 6 | devmod.sqlite 7 | devmod.db 8 | db 9 | ### Windows template 10 | # Windows thumbnail cache files 11 | Thumbs.db 12 | ehthumbs.db 13 | ehthumbs_vista.db 14 | 15 | # Dump file 16 | *.stackdump 17 | 18 | # Folder config file 19 | [Dd]esktop.ini 20 | 21 | # Recycle Bin used on file shares 22 | $RECYCLE.BIN/ 23 | 24 | # Windows Installer files 25 | *.cab 26 | *.msi 27 | *.msm 28 | *.msp 29 | 30 | # Windows shortcuts 31 | *.lnk 32 | ### Node template 33 | # Logs 34 | logs 35 | *.log 36 | npm-debug.log* 37 | yarn-debug.log* 38 | yarn-error.log* 39 | 40 | # Runtime data 41 | pids 42 | *.pid 43 | *.seed 44 | *.pid.lock 45 | 46 | # Directory for instrumented libs generated by jscoverage/JSCover 47 | lib-cov 48 | 49 | # Coverage directory used by tools like istanbul 50 | coverage 51 | 52 | # nyc test coverage 53 | .nyc_output 54 | 55 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 56 | .grunt 57 | 58 | # Bower dependency directory (https://bower.io/) 59 | bower_components 60 | 61 | # node-waf configuration 62 | .lock-wscript 63 | 64 | # Compiled binary addons (https://nodejs.org/api/addons.html) 65 | build/Release 66 | 67 | # Dependency directories 68 | jspm_packages/ 69 | 70 | # Typescript v1 declaration files 71 | typings/ 72 | 73 | # Optional npm cache directory 74 | .npm 75 | 76 | # Optional eslint cache 77 | .eslintcache 78 | 79 | # Optional REPL history 80 | .node_repl_history 81 | 82 | # Output of 'npm pack' 83 | *.tgz 84 | 85 | # Yarn Integrity file 86 | .yarn-integrity 87 | 88 | # dotenv environment variables file 89 | .env 90 | 91 | # next.js build output 92 | .next 93 | ### Linux template 94 | *~ 95 | 96 | # temporary files which can be created if a process still has a handle open of a deleted file 97 | .fuse_hidden* 98 | 99 | # KDE directory preferences 100 | .directory 101 | 102 | # Linux trash folder which might appear on any partition or disk 103 | .Trash-* 104 | 105 | # .nfs files are created when an open file is removed but is still being accessed 106 | .nfs* 107 | ### JetBrains template 108 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 109 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 110 | 111 | # User-specific stuff: 112 | .idea/**/workspace.xml 113 | .idea/**/tasks.xml 114 | .idea/dictionaries 115 | 116 | # Sensitive or high-churn files: 117 | .idea/**/dataSources/ 118 | .idea/**/dataSources.ids 119 | .idea/**/dataSources.xml 120 | .idea/**/dataSources.local.xml 121 | .idea/**/sqlDataSources.xml 122 | .idea/**/dynamic.xml 123 | .idea/**/uiDesigner.xml 124 | 125 | # Gradle: 126 | .idea/**/gradle.xml 127 | .idea/**/libraries 128 | 129 | # CMake 130 | cmake-build-debug/ 131 | cmake-build-release/ 132 | 133 | # Mongo Explorer plugin: 134 | .idea/**/mongoSettings.xml 135 | 136 | ## File-based project format: 137 | *.iws 138 | 139 | ## Plugin-specific files: 140 | 141 | # IntelliJ 142 | out/ 143 | 144 | # mpeltonen/sbt-idea plugin 145 | .idea_modules/ 146 | 147 | # JIRA plugin 148 | atlassian-ide-plugin.xml 149 | 150 | # Cursive Clojure plugin 151 | .idea/replstate.xml 152 | 153 | # Crashlytics plugin (for Android Studio and IntelliJ) 154 | com_crashlytics_export_strings.xml 155 | crashlytics.properties 156 | crashlytics-build.properties 157 | fabric.properties 158 | ### macOS template 159 | # General 160 | .DS_Store 161 | .AppleDouble 162 | .LSOverride 163 | 164 | # Icon must end with two \r 165 | Icon 166 | 167 | # Thumbnails 168 | ._* 169 | 170 | # Files that might appear in the root of a volume 171 | .DocumentRevisions-V100 172 | .fseventsd 173 | .Spotlight-V100 174 | .TemporaryItems 175 | .Trashes 176 | .VolumeIcon.icns 177 | .com.apple.timemachine.donotpresent 178 | 179 | # Directories potentially created on remote AFP share 180 | .AppleDB 181 | .AppleDesktop 182 | Network Trash Folder 183 | Temporary Items 184 | .apdisk 185 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | DevMod -------------------------------------------------------------------------------- /.idea/DevMod.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevMod 2 | 3 | > A Discord bot for moderating servers. 4 | 5 | This was originally made to moderate the DevCord Discord community, but you 6 | don't need to be a developer to use it. 7 | 8 | # Project Outdated and Unsupported! 9 | > devmod v4 is now deprecated! 10 | 11 | The current version of devmod is available at [redxtech/devmod](/redxtech/devmod). 12 | This version of devmod is now unsupported. The current version has all of the same 13 | features and more, all while running faster and having cleaner code. There isn't a 14 | situation that I would recommend using this version instead of the newest version, 15 | so all issues opened here will just be closed with this explaination. 16 | 17 | ## Setup 18 | 19 | ### Step 1 - Creating the Bot User 20 | 21 | First, go to the 22 | [Discord Developers](https://discordapp.com/developers/applications/me) 23 | page and create a new app. Name the app and give it a profile picture as you 24 | please. Afterwards, navigate to the "Bot" section. Click on "Add Bot" under the 25 | "Build-a-Bot" subsection. A popup will appear asking you to confirm the action. 26 | Once you confirm the action, the bot section will expand and you will be able to 27 | obtain your token under the "Token" section. This token will allow your bot to 28 | log in to Discord. 29 | 30 | ### Step 2 - Inviting the Bot to your Server 31 | 32 | Navigate to the "OAuth2" section and head to the "OAuth2 URL Generator" 33 | subsection. Under the "Scopes" section, click on "bot". A new section, titled 34 | "Bot Permissions", will appear. Select "Administrator. This will allow the bot 35 | to see all of the channels, ban users, etc. Afterwards, look at the "Scopes" 36 | section. At the bottom, there is a URL contained within a field. Copy and paste 37 | the URL into a new browser tab. A list of servers in which you have 38 | administrator permissions will appear. After choosing a server and clicking 39 | "Authorize", the bot will join the selected server. 40 | 41 | ### Step 3 - Setting Up the Host Machine 42 | 43 | Because the bot run on [node](https://nodejs.org), you must have it installed 44 | on your host machine. If you are familar with git, clone the DevMod repo using 45 | `git clone`. Else, download the repo as a zip and unpack it to any folder of 46 | your choosing. Open up the command line and `cd` into the folder that contains 47 | the cloned/unzipped code. 48 | 49 | Run `npm install` to install all of the bot's dependencies and then `npm install 50 | -g pm2` to be able to run the bot in the background. The host machine is now 51 | configured to run the bot. All you need to do now is set up the config. 52 | 53 | ### Step 4 - Configuring the bot 54 | 55 | This part is easy. Just type in `npm run env` and it will ask you some 56 | questions. 57 | 58 | Option | Default | Description 59 | ---|---|--- 60 | `BOT_TOKEN` | | The token you got from creating the bot user. 61 | `OWNER_ID` | | Your [Discord ID](https://goo.gl/fTsqkq). 62 | `PREFIX` | `.` | The prefix to start off all bot commands. 63 | `MSG_DELETE_TIME` | `10` | The amount of seconds before deleting the help commands. 64 | `DB_FILE` | `devmod.sqlite` | The name of the database file for warnings and points. 65 | `AUTOBAN` | `true` | Whether or not to automatically ban a user after hitting the maximum amount of warnings. 66 | `AUTOBAN_WARNS` | `3` | The number warnings are needed to autoban a user. 67 | `BAN_MSG_DELETE` | `0` | The number of days of a user's messages to delete after being banned. 68 | `CHANNEL_LOG_WARN` | `warnings` | The name of the channel to log warnings. 69 | `CHANNEL_LOG_BAN` | `bans` | The name of the channel to log bans. 70 | `CHANNEL_LOG_REPORT` | `reports` | The name of the channel to log reports. 71 | `POINTS_EMOJI` | `boye` | The name of the server specific emoji to append to Good Boye Points. 72 | `STATUS_INTERVAL` | `5` | The amount of minutes between changing the bot's status. 73 | `POINTS_TOP_COUNT` | `10` | The amount of users to show in Good Boye Points top and bottom commands. 74 | 75 | Afterwards, open up `src/approvedRoles.js`. Add role names that you would like 76 | members to be able to assign to themselves via the role command. This is case 77 | sensitive, so be sure to get it 100% accurate. 78 | 79 | ### Step 5 - Running the Bot 80 | 81 | Now that you have all of the options set up, you can run the bot. If you want to 82 | run it on your computer while keeping the command line window open, use the 83 | command `npm run start`. 84 | 85 | If you want to run the bot in the background without needing to keep a command 86 | line window open, use the command `pm2 start devmod.js`. 87 | 88 | Now the bot is ready for use! 89 | 90 | > Just a quick note - the tags in `src/tags.js`, the activities in 91 | `src/activities.js`, and the roles in `src/approvedRoles.js` may not be specific 92 | to all servers, so make sure to check those out before running the bot. 93 | 94 | ## Features & Usage 95 | 96 | > This overview assumes that all config options are set to their defaults. 97 | 98 | ### Warning Users 99 | 100 | If a user has a role that allows them to kick users, they can use the 101 | warns system. 102 | 103 | `.warn ` will send a DM to the user with their warning, send a 104 | message to the channel specified in `CHANNEL_LOG_WARN`, and adds the warning to 105 | the database file along with the ID of the user who issued it. If a user already 106 | has 2 warnings, they will be banned. A DM will be sent to them with the reason 107 | and it will be logged to the bans channel. Once they are banned, all of their 108 | warnings are removed from the database. 109 | 110 | `.warnlist ` (or `.warns `) will list all of the warnings a user 111 | has, the reasons for these warnings, and the people who warned them. 112 | 113 | `.clearwarns []` (or `.cwarns []`) will clear the 114 | specified amount of warnings from a user. If no amount is specified, all 115 | warnings will be removed. 116 | 117 | ### Banning Users 118 | 119 | If a user has a role that allows them to ban users, they can use the ban 120 | command. 121 | 122 | `.ban []` will ban a user for the specified reason. If 123 | `--rm` is included, their messages from the past 7 days will be deleted. 124 | 125 | ### Unbanning Users 126 | 127 | If a user has a role that allows them to ban user, they are able to use 128 | the unban command. 129 | 130 | `.unban []` will unban a user for the specified reason. If no 131 | reason is specified, "Unbanned via unban command." will be used. `` is 132 | able to be something that can be parsed as a user: their case-sensitive tag 133 | (eg. `RedXTech#1234`), their case-sensitive username (eg. `RedXTech`), or their 134 | [unique ID]([Discord ID](https://goo.gl/fTsqkq)). 135 | 136 | ### Reporting Users 137 | 138 | Any user can use the report command. 139 | 140 | `.report ` will send a message into the reports channel with the 141 | message, the name of the user who sent it, and the channel that it was sent 142 | from. 143 | 144 | ### Tags 145 | 146 | Any user is able to use tags and list tags. 147 | 148 | `.tag []` will call up a tag. If `` is specified, the bot will 149 | tag the user when sending the message. 150 | 151 | `.tags` will show a list of tags. This list will be deleted after 10 seconds. 152 | 153 | > Tags commands can be used in DMs. 154 | 155 | ### Good Boye Points 156 | 157 | Any user can see the top/bottom lists, their own GBP score, and the GBP scores 158 | of others. However, if they do not have permission to kick users, they cannot 159 | add, remove, or set the GBP for anyone. 160 | 161 | `.gbp top` will show the 10 users with most points. 162 | 163 | `.gbp bottom` will show the 10 users with the least points. 164 | 165 | `.gbp show []` will show the GBP of a user. If no user is specified, the 166 | user who called the command will be used. 167 | 168 | `.gbp add []` will add the specified amount of points to a user. 169 | If no amount is specified, 1 is used. 170 | 171 | `.gbp rm []` will remove the specified amount of points to a 172 | user. If no amount is specified, 1 point will be deducted. 173 | 174 | `.gbp set []` will set the amount of points for the specified 175 | user. 176 | 177 | `.gbp help` will show the user how to use the GBP system. 178 | 179 | ### Roles System 180 | 181 | Anyone can use the role command. It will add & remove roles that have been 182 | whitelisted in `src/approvedRoles.js`. 183 | 184 | `.role add ` will add the specified role. 185 | 186 | `role rm ` will remove the specified role. 187 | 188 | Both commands are case sensitive. 189 | 190 | ### Prune 191 | 192 | Anyone with MANAGE_MESSAGES permission can use this command. 193 | 194 | `.prune []` will remove the specified amount of commands from the 195 | current channel. If no number is specified, the default is 5. 196 | 197 | ### Stats & Users Commands 198 | 199 | These commands can be used by all users on the server. 200 | 201 | `.stats` will display stats about the user. 202 | 203 | `.users` will display the number of users on the server. 204 | 205 | ### LMGTFY 206 | 207 | This command can be used by anyone. 208 | 209 | `.lmgtfy ` will send a link with the LMGTFY query. 210 | 211 | ### Help 212 | 213 | Anyone can use this command. 214 | 215 | `.help` will send a message with a list of commands and how to use them. The 216 | message will be deleted after 10 seconds. 217 | 218 | > Help command can be used in DMs. 219 | 220 | ### Ping 221 | 222 | Anyone can use this command. 223 | 224 | `.ping` will send a message to the chat with the ping and round trip time of the 225 | bot. 226 | 227 | > Ping command can be used in DMs. 228 | 229 | ## Future Ideas 230 | 231 | - Add configurable permission levels for commands. 232 | - Add configuration for whether to delete warns on a ban. 233 | - Add option to prevent certain user from using report command. 234 | - Add usage statistics to DB. 235 | -------------------------------------------------------------------------------- /devmod.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * Add @babel/register to run in node. 4 | */ 5 | require('@babel/register') 6 | require('./index') 7 | -------------------------------------------------------------------------------- /env.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "BOT_TOKEN" 4 | }, 5 | { 6 | "name": "OWNER_ID" 7 | }, 8 | { 9 | "name": "PREFIX", 10 | "defaultOption": "." 11 | }, 12 | { 13 | "name": "MSG_DELETE_TIME", 14 | "defaultOptions": "10" 15 | }, 16 | { 17 | "name": "DB_FILE", 18 | "defaultOption": "devmod.sqlite" 19 | }, 20 | { 21 | "name": "AUTOBAN", 22 | "defaultOption": "true" 23 | }, 24 | { 25 | "name": "AUTOBAN_WARNS", 26 | "defaultOption": "3" 27 | }, 28 | { 29 | "name": "BAN_MSG_DELETE", 30 | "defaultOption": "0" 31 | }, 32 | { 33 | "name": "CHANNEL_LOG_WARN", 34 | "defaultOption": "warnings" 35 | }, 36 | { 37 | "name": "CHANNEL_LOG_BAN", 38 | "defaultOption": "bans" 39 | }, 40 | { 41 | "name": "CHANNEL_REPORT", 42 | "defaultOption": "reports" 43 | }, 44 | { 45 | "name": "CHANNEL_ROLES", 46 | "defaultOption": "roles" 47 | }, 48 | { 49 | "name": "MUTED_ROLE", 50 | "defaultOption": "muted" 51 | }, 52 | { 53 | "name": "POINTS_EMOJI", 54 | "defaultOption": "boye" 55 | }, 56 | { 57 | "name": "STATUS_INTERVAL", 58 | "defaultOption": "5" 59 | }, 60 | { 61 | "name": "POINTS_TOP_COUNT", 62 | "defaultOption": "10" 63 | } 64 | ] -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * Main file that calls all other files and specifies actions. 4 | */ 5 | import { AkairoClient } from 'discord-akairo' 6 | import { join } from 'path' 7 | 8 | import { botToken, ownerID, prefix, statusInterval } from './src/config' 9 | import activities from './src/activities' 10 | import { roleAdd, roleRm } from './src/reactionRoles' 11 | import { migrate } from './src/utils' 12 | 13 | const client = new AkairoClient({ 14 | ownerID, 15 | prefix, 16 | allowMention: true, 17 | handleEdits: true, 18 | commandUtil: true, 19 | commandUtilLifetime: 600000, 20 | commandDirectory: join(__dirname, 'src', 'commands') 21 | }) 22 | 23 | const run = async () => { 24 | try { 25 | await client.login(botToken) 26 | console.log(`Logged in as ${client.user.tag}.`) 27 | client.user.setActivity('.help') 28 | 29 | setInterval(async () => { 30 | const activity = activities[Math.floor(Math.random() * activities.length)] 31 | const newActivity = await client.user.setActivity(activity) 32 | setTimeout(async () => { 33 | await client.user.setActivity('.help') 34 | }, 60000) 35 | }, statusInterval * 60000) 36 | 37 | client.on('raw', async event => { 38 | try { 39 | const { d: data } = event 40 | if (event.t === 'MESSAGE_REACTION_ADD') { 41 | roleAdd( 42 | client, 43 | data.guild_id, 44 | data.message_id, 45 | data.user_id, 46 | data.emoji.name 47 | ) 48 | } else if (event.t === 'MESSAGE_REACTION_REMOVE') { 49 | roleRm( 50 | client, 51 | data.guild_id, 52 | data.message_id, 53 | data.user_id, 54 | data.emoji.name 55 | ) 56 | } 57 | } catch (e) { 58 | console.log(`Error handling reaction: ${e}`) 59 | } 60 | }) 61 | 62 | // Create schema 63 | await migrate() 64 | } catch (e) { 65 | console.log(`Error running bot: ${e}`) 66 | } 67 | } 68 | 69 | run() 70 | 71 | process.on('uncaughtException', (e, p) => { 72 | console.log(`Uncaught Exception: ${e} && ${p}`) 73 | client.destroy() 74 | process.exit(1) 75 | }) 76 | -------------------------------------------------------------------------------- /migrations/001_create_schema.sql: -------------------------------------------------------------------------------- 1 | create table if not exists warnings ( 2 | id int primary key, 3 | discord_id text not null, 4 | reason text not null, 5 | `date` date not null, 6 | mod_id text not null 7 | ); 8 | 9 | create table if not exists bans ( 10 | id int primary key, 11 | discord_id text not null, 12 | discord_name text not null, 13 | reason text not null, 14 | `date` date not null, 15 | mod_id text not null 16 | ); 17 | 18 | create table if not exists points ( 19 | id int primary key, 20 | discord_id text not null unique, 21 | points int not null 22 | ); 23 | 24 | create table if not exists settings ( 25 | id int primary key, 26 | key text not null unique, 27 | value text not null 28 | ); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devmod", 3 | "version": "1.0.0", 4 | "description": "Bot for moderating the DevCord server.", 5 | "main": "src/index.js", 6 | "license": "MIT", 7 | "author": "Gabe Dunn (https://github.com/redxtech)", 8 | "scripts": { 9 | "build": "parcel build index.js -d dist -o devmod -t node --no-source-maps", 10 | "dev": "nodemon --exec babel-node index.js", 11 | "env": "envup", 12 | "lint": "eslint --ext .js src", 13 | "lint:fix": "eslint --ext .js src --fix", 14 | "send": "babel-node src/sendRolesMessage.js", 15 | "start": "babel-node index.js" 16 | }, 17 | "devDependencies": { 18 | "@babel/cli": "^7.2.3", 19 | "@babel/core": "^7.2.2", 20 | "@babel/node": "^7.2.2", 21 | "@babel/preset-env": "^7.2.3", 22 | "@babel/register": "^7.0.0", 23 | "envup": "^1.1.2", 24 | "eslint": "^5.11.0", 25 | "eslint-config-standard": "^12.0.0", 26 | "eslint-plugin-import": "^2.14.0", 27 | "eslint-plugin-node": "^8.0.0", 28 | "eslint-plugin-promise": "^4.0.1", 29 | "eslint-plugin-standard": "^4.0.0", 30 | "nodemon": "^1.18.9", 31 | "parcel-bundler": "^1.11.0" 32 | }, 33 | "dependencies": { 34 | "better-sqlite3": "^5.2.1", 35 | "bufferutil": "^4.0.1", 36 | "discord-akairo": "^7.5.5", 37 | "discord.js": "^11.4.2", 38 | "dotenv": "^6.2.0", 39 | "erlpack": "^0.1.2", 40 | "libsodium-wrappers": "^0.7.3", 41 | "moment": "^2.23.0", 42 | "node-opus": "^0.3.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/activities.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * Config file for available activities. 4 | */ 5 | export default [ 6 | 'Counting your Good Boye Points.', 7 | 'Trying to unban SWAT SEAL.', 8 | 'Spamming the DMs of a random user.', 9 | 'Compiling.', 10 | 'Having a snack.', 11 | 'uncaughtException', 12 | 'Shitposting in #off-topic-chat.', 13 | 'Admiring egg.', 14 | 'Trying to exit vim.', 15 | 'BEING A HUMAN.', 16 | '10011001101111100000011011110', 17 | 'Hacking the FBI.', 18 | 'Serving NaN users.', 19 | 'on a Christian Server', 20 | 'et ur brokli', 21 | 'this chat gets weird fasy', 22 | 'https://i.imgur.com/BVRHZzg.png' 23 | ] 24 | -------------------------------------------------------------------------------- /src/approvedRoles.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * Config file for approved roles. 4 | */ 5 | export const rolesMessages = [ 6 | { 7 | name: 'Descriptive Roles', 8 | message: 9 | 'These roles are meant to be descriptive about what you do. You' + 10 | ' can add these by reacting to this message with the corresponding emoji.', 11 | roles: { 12 | developer: { name: 'Developer', emoji: '☕' }, 13 | helper: { name: 'Helper (general)', emoji: '🚁' }, 14 | 'javascript-help': { name: 'JavasScript Helper', emoji: '🖥' }, 15 | 'css-html-help': { name: 'CSS/HTML Helper', emoji: '📰' }, 16 | 'ui-help': { name: 'UI Helper', emoji: '📱' }, 17 | 'php-help': { name: 'PHP Helper', emoji: '♦' } 18 | } 19 | } 20 | ] 21 | 22 | export const allRoles = {} 23 | export const allRolesMap = {} 24 | 25 | for (const roles of rolesMessages) { 26 | for (const role of Object.entries(roles.roles)) { 27 | allRoles[role[0]] = role[1].name 28 | allRolesMap[role[0]] = role[1] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/colours.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * Config files for available colours. 4 | */ 5 | export default { 6 | red: 0xE74C3C, 7 | orange: 0xF39C12, 8 | yellow: 0xF1C40F, 9 | green: 0x2ECC71, 10 | blue: 0x3498DB, 11 | grey: 0x34495E, 12 | purple: 0x9932CC 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/about.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * File that handles the about command. 4 | */ 5 | 6 | import { Command } from 'discord-akairo' 7 | import colours from '../colours' 8 | import { msgDeleteTime } from '../config' 9 | 10 | export default class PingCommand extends Command { 11 | constructor () { 12 | super('about', { 13 | aliases: ['about'], 14 | category: 'util', 15 | description: 'Tells a little bit about the bot.', 16 | cooldown: 1000 * msgDeleteTime, 17 | ratelimit: 1 18 | }) 19 | } 20 | 21 | // noinspection JSMethodCanBeStatic 22 | async exec (message) { 23 | try { 24 | await message.delete(1) 25 | const user = message.member ? message.member.user : message.author 26 | return message.util.send({ 27 | embed: { 28 | title: 'DevMod - About the Bot', 29 | color: colours.blue, 30 | url: 'https://github.com/redxtech/devmod', 31 | description: 'DevMod is a bot made for the DevCord community, but' + 32 | ' is applicable to any server that needs moderating. It is written' + 33 | ' with discord-akairo and discord.js. To use it on your own' + 34 | ' server, follow the steps in the GitHub repo.', 35 | fields: [ 36 | { 37 | name: 'Author:', 38 | value: '<@170451883134156800>', 39 | inline: true 40 | }, 41 | { 42 | name: 'GitHub Repo:', 43 | value: 'https://github.com/redxtech/devmod', 44 | inline: true 45 | } 46 | ], 47 | author: { 48 | name: user.username, 49 | icon_url: user.avatarURL 50 | }, 51 | timestamp: new Date() 52 | } 53 | }) 54 | } catch (e) { 55 | console.log(`Error sending about message: ${e}`) 56 | return null 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/commands/ban.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the ban command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import Database from 'better-sqlite3' 7 | import colours from '../colours' 8 | 9 | import { errorMessage } from '../common' 10 | import { channels, dbFile } from '../config' 11 | 12 | export default class BanCommand extends Command { 13 | constructor () { 14 | super('ban', { 15 | aliases: ['ban'], 16 | category: 'moderation', 17 | description: 'Bans a user.', 18 | args: [ 19 | { 20 | id: 'member', 21 | type: 'member' 22 | }, 23 | { 24 | id: 'reason', 25 | match: 'rest' 26 | }, 27 | { 28 | id: 'rm', 29 | match: 'flag', 30 | prefix: '--rm' 31 | } 32 | ], 33 | clientPermissions: ['BAN_MEMBERS'], 34 | userPermissions: ['BAN_MEMBERS'], 35 | channelRestriction: 'guild' 36 | }) 37 | } 38 | 39 | // noinspection JSMethodCanBeStatic 40 | async exec (message, args) { 41 | try { 42 | if (!args.member) { 43 | await message.react('❌') 44 | const embed = errorMessage( 45 | 'Member Not Found', 46 | 'No member found with that name.' 47 | ) 48 | return message.util.send({ embed }) 49 | } 50 | if (!args.reason) { 51 | await message.react('❌') 52 | const embed = errorMessage( 53 | 'Reason Not Specified', 54 | 'Please specify a reason for the ban.' 55 | ) 56 | return message.util.send({ embed }) 57 | } 58 | if (args.member.permissions.has('KICK_MEMBERS')) { 59 | await message.react('❌') 60 | const embed = errorMessage( 61 | "Can't Ban Member", 62 | 'You are not allowed' + ' to ban that member.' 63 | ) 64 | return message.util.send({ embed }) 65 | } 66 | await message.delete(1) 67 | 68 | const user = args.member.user 69 | 70 | const time = args.rm ? 7 : 0 71 | 72 | await user.send({ 73 | embed: { 74 | title: `You have been banned from ${message.guild.name}.`, 75 | color: colours.red, 76 | thumbnail: { 77 | url: message.guild.iconURL 78 | }, 79 | fields: [ 80 | { 81 | name: 'Reason:', 82 | value: args.reason 83 | } 84 | ], 85 | footer: { 86 | text: `Your messages from the past ${time} days have been deleted.` 87 | } 88 | } 89 | }) 90 | await message.guild.channels 91 | .find(c => c.name === channels.ban) 92 | .send({ 93 | embed: { 94 | color: colours.red, 95 | title: 'Ban', 96 | description: `${user} has been banned`, 97 | author: { 98 | name: message.member.user.username, 99 | icon_url: message.member.user.avatarURL 100 | }, 101 | fields: [ 102 | { 103 | name: 'Reason:', 104 | value: args.reason 105 | } 106 | ], 107 | footer: { 108 | icon_url: user.avatarURL, 109 | text: `${ 110 | user.tag 111 | }'s messages from the past ${time} days have been deleted.` 112 | }, 113 | timestamp: new Date() 114 | } 115 | }) 116 | await args.member.ban({ 117 | days: time, 118 | reason: args.reason 119 | }) 120 | // Add ban information to bans database. 121 | try { 122 | const db = new Database(dbFile) 123 | 124 | db.prepare('DELETE FROM warnings WHERE discord_id = ?').run(user.id) 125 | 126 | return db 127 | .prepare( 128 | 'INSERT INTO bans (discord_id, discord_name, reason, date, mod_id) VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?)' 129 | ) 130 | .run(user.id, user.tag, args.reason, message.member.user.id) 131 | } catch (e) { 132 | console.log(`Accessing DB failed: ${e}`) 133 | } 134 | } catch (e) { 135 | console.log(`Ban command failed: ${e}`) 136 | return null 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/commands/clearWarns.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the clearWarns command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import Database from 'better-sqlite3' 7 | import colours from '../colours' 8 | 9 | import { errorMessage } from '../common' 10 | import { dbFile } from '../config' 11 | 12 | export default class ClearWarnsCommand extends Command { 13 | constructor () { 14 | super('clearwarns', { 15 | aliases: ['clearwarns', 'cwarns'], 16 | category: 'moderation', 17 | description: 'Clears warning from a user.', 18 | args: [ 19 | { 20 | id: 'member', 21 | type: 'member' 22 | }, 23 | { 24 | id: 'amount', 25 | default: '*' 26 | } 27 | ], 28 | clientPermissions: ['KICK_MEMBERS'], 29 | userPermissions: ['KICK_MEMBERS'], 30 | channelRestriction: 'guild' 31 | }) 32 | } 33 | 34 | // noinspection JSMethodCanBeStatic 35 | async exec (message, args) { 36 | try { 37 | if (!args.member) { 38 | await message.react('❌') 39 | const embed = errorMessage( 40 | 'Member Not Found', 41 | 'No member found with that name.' 42 | ) 43 | return message.util.send({ embed }) 44 | } 45 | 46 | // Set amount to number or * based on args. 47 | let amount 48 | if (args.amount !== '*' && isNaN(Number(args.amount))) { 49 | amount = 1 50 | } else if (args.amount === '*') { 51 | amount = '*' 52 | } else { 53 | amount = Number(args.amount) 54 | } 55 | 56 | if (amount < 1) { 57 | await message.react('❌') 58 | const embed = errorMessage( 59 | 'Invalid Number', 60 | 'The number of warns to delete must be at least 1.' 61 | ) 62 | return message.util.send({ embed }) 63 | } 64 | await message.delete(1) 65 | 66 | const reply = async () => { 67 | let desc, desc2 68 | if (amount === '*') { 69 | desc = 'Warnings' 70 | desc2 = 'have' 71 | } else if (amount === 1) { 72 | desc = `${amount} warning` 73 | desc2 = 'has' 74 | } else { 75 | desc = `${amount} warnings` 76 | desc2 = 'have' 77 | } 78 | return message.util.send({ 79 | embed: { 80 | color: colours.green, 81 | title: 'Removed Warnings', 82 | author: { 83 | name: message.member.user.username, 84 | icon_url: message.member.user.avatarURL 85 | }, 86 | description: `${desc} ${desc2} been removed from ${args.member}.`, 87 | timestamp: new Date() 88 | } 89 | }) 90 | } 91 | // Delete warnings from database based on amount arg 92 | try { 93 | const db = new Database(dbFile) 94 | const { 95 | member: { 96 | user: { id } 97 | } 98 | } = args 99 | 100 | if (amount === '*') { 101 | db.prepare('DELETE FROM warnings WHERE discord_id = ?').run(id) 102 | return reply() 103 | } 104 | 105 | db.prepare( 106 | 'DELETE FROM warnings WHERE id IN (SELECT id FROM warnings WHERE discord_id = ? ORDER BY id DESC LIMIT ?)' 107 | ).run(id, amount) 108 | return reply() 109 | } catch (e) { 110 | console.log(`Error setting reply: ${e}`) 111 | return null 112 | } 113 | } catch (e) { 114 | console.log(`Clear Warns command failed: ${e}`) 115 | return null 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/commands/gbp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the gbp command. 4 | * 5 | * TODO: XD rewrite pls, modularize a bit, 6 | */ 7 | import { Command } from 'discord-akairo' 8 | import Database from 'better-sqlite3' 9 | import colours from '../colours' 10 | 11 | import { errorMessage } from '../common' 12 | import { dbFile, msgDeleteTime, pointEmoji, pointsTopCount } from '../config' 13 | 14 | export default class GBPCommand extends Command { 15 | constructor () { 16 | super('gbp', { 17 | aliases: ['gbp'], 18 | category: 'fun', 19 | description: 'Operations involving GBP.', 20 | args: [ 21 | { 22 | id: 'command' 23 | }, 24 | { 25 | id: 'member', 26 | type: 'member', 27 | default: message => message.member 28 | }, 29 | { 30 | id: 'points', 31 | type: 'integer', 32 | default: 1 33 | } 34 | ], 35 | channelRestriction: 'guild' 36 | }) 37 | } 38 | 39 | // noinspection JSMethodCanBeStatic 40 | async exec (message, args) { 41 | try { 42 | if (!args.command) { 43 | await message.react('❌') 44 | const embed = errorMessage( 45 | 'No Command Specified', 46 | 'You need to' + ' specify a command to use.' 47 | ) 48 | return message.util.send({ embed }) 49 | } 50 | const embed = { 51 | title: 'Good Boye Points', 52 | color: colours.green, 53 | author: { 54 | name: message.member.user.username, 55 | icon_url: message.member.user.avatarURL 56 | } 57 | } 58 | try { 59 | const db = new Database(dbFile) 60 | 61 | const boye = 62 | message.guild.emojis.find(e => e.name === pointEmoji) || ':)' 63 | let count, points, newPoints, stmt, row 64 | // Switch for different commands based on args. 65 | switch (args.command) { 66 | case 'top': 67 | const topUsers = [] 68 | count = 0 69 | 70 | stmt = db.prepare( 71 | 'SELECT * FROM points ORDER BY points DESC LIMIT ?' 72 | ) 73 | 74 | for (const row of stmt.iterate(pointsTopCount)) { 75 | const member = message.guild.member(row.discord_id) 76 | 77 | if (member !== null) { 78 | const { user } = member 79 | topUsers.push({ 80 | name: `#${++count}: ${user.tag}.`, 81 | value: `${row.points} GBP ${boye}` 82 | }) 83 | } 84 | } 85 | 86 | if (count === 0) { 87 | embed.description = 'No users have GBP yet.' 88 | } 89 | embed.fields = topUsers 90 | await message.delete(1) 91 | return message.util.send({ embed }) 92 | case 'bottom': 93 | const bottomUsers = [] 94 | 95 | // todo: just ew.. 96 | count = db.prepare('SELECT COUNT(*) AS cnt FROM points').get().cnt 97 | 98 | if (count === 0) { 99 | embed.description = 'No users have GBP yet.' 100 | } else { 101 | stmt = db.prepare( 102 | 'SELECT * FROM points ORDER BY points ASC LIMIT ?' 103 | ) 104 | 105 | for (const row of stmt.iterate(pointsTopCount)) { 106 | const member = message.guild.member(row.discord_id) 107 | if (member !== null) { 108 | const { user } = member 109 | bottomUsers.push({ 110 | name: `#${count--}: ${user.tag}.`, 111 | value: `${row.points} GBP ${boye}` 112 | }) 113 | } 114 | } 115 | } 116 | 117 | embed.fields = bottomUsers 118 | await message.delete(1) 119 | return message.util.send({ embed }) 120 | case 'show': 121 | if (!args.member) { 122 | await message.react('❌') 123 | const embed = errorMessage( 124 | 'Member Not Found', 125 | 'No member found with that name.' 126 | ) 127 | return message.util.send({ embed }) 128 | } 129 | const { user } = message.guild.member(args.member.id) 130 | if (user === null) { 131 | await message.react('❌') 132 | return message.util.send({ 133 | embed: errorMessage( 134 | 'Member Not Found', 135 | 'No member found with that name.' 136 | ) 137 | }) 138 | } 139 | 140 | const res = db 141 | .prepare('SELECT points as gbp FROM points WHERE discord_id = ?') 142 | .get(args.member.user.id) 143 | 144 | const gbp = res ? res.gbp : 0 145 | 146 | embed.description = `${args.member} has ${gbp} GBP ${boye}` 147 | await message.delete(1) 148 | return message.util.send({ embed }) 149 | case 'add': 150 | if (!message.member.permissions.has('KICK_MEMBERS')) { 151 | await message.react('❌') 152 | return message.util.send({ 153 | embed: errorMessage( 154 | 'No Permission', 155 | 'You are not allowed to use that command.' 156 | ) 157 | }) 158 | } 159 | 160 | row = db 161 | .prepare('SELECT `points` FROM `points` WHERE `discord_id` = ?') 162 | .get(args.member.user.id) 163 | points = row && row.points 164 | 165 | newPoints = (points | 0) + args.points 166 | db.prepare( 167 | 'INSERT OR REPLACE INTO `points` (`discord_id`, `points`) VALUES (?, ?)' 168 | ).run(args.member.user.id, newPoints) 169 | 170 | embed.description = `${ 171 | args.member 172 | } now has ${newPoints} GBP ${boye}` 173 | await message.delete(1) 174 | return message.util.send({ embed }) 175 | case 'rm': 176 | if (!message.member.permissions.has('KICK_MEMBERS')) { 177 | await message.react('❌') 178 | return message.util.send({ 179 | embed: errorMessage( 180 | 'No Permission', 181 | 'You are not allowed to use that command.' 182 | ) 183 | }) 184 | } 185 | 186 | row = db 187 | .prepare('SELECT `points` FROM `points` WHERE `discord_id` = ?') 188 | .get(args.member.user.id) 189 | 190 | newPoints = ((row && row.points) | 0) - args.points 191 | db.prepare( 192 | 'INSERT OR REPLACE INTO `points` (`discord_id`, `points`) VALUES (?, ?)' 193 | ).run(args.member.user.id, newPoints) 194 | 195 | embed.color = colours.red 196 | embed.description = `${ 197 | args.member 198 | } now has ${newPoints} GBP ${boye}` 199 | await message.delete(1) 200 | return message.util.send({ embed }) 201 | case 'set': 202 | if (!message.member.permissions.has('KICK_MEMBERS')) { 203 | await message.react('❌') 204 | return message.util.send({ 205 | embed: errorMessage( 206 | 'No Permission', 207 | 'You are not allowed to use that command.' 208 | ) 209 | }) 210 | } 211 | db.prepare( 212 | 'INSERT OR REPLACE INTO `points` (`discord_id`,' + 213 | ' `points`) VALUES (?, ?)' 214 | ).run(args.member.user.id, args.points) 215 | embed.color = colours.blue 216 | embed.description = `${args.member} now has ${ 217 | args.points 218 | } GBP ${boye}` 219 | await message.delete(1) 220 | return message.util.send({ embed }) 221 | case 'help': 222 | embed.fields = [ 223 | { 224 | name: '.gbp help', 225 | value: 'Show this message.' 226 | }, 227 | { 228 | name: '.gbp top', 229 | value: 'Show 3 users with most GBPs.' 230 | }, 231 | { 232 | name: '.gbp bottom', 233 | value: 'Show 3 users with least GBPs.' 234 | }, 235 | { 236 | name: '.gbp show []', 237 | value: 238 | "Show a user's GBPs. If no user is specified, self is used." 239 | }, 240 | { 241 | name: '.gbp add [ (default 1)]', 242 | value: 'Adds specified points to a user.' 243 | }, 244 | { 245 | name: '.gbp rm [ (default 1)]', 246 | value: 'Remove specified points from a user.' 247 | } 248 | ] 249 | embed.color = colours.blue 250 | const helpMsg = await message.channel.send({ embed }) 251 | await message.delete(1) 252 | return setTimeout(() => { 253 | helpMsg.delete(1) 254 | }, msgDeleteTime * 1000) 255 | default: 256 | await message.react('❌') 257 | return message.util.send({ 258 | embed: errorMessage('Wrong Usage', 'That is not a GBP command.') 259 | }) 260 | } 261 | } catch (e) { 262 | console.log(`Error accessing database: ${e}`) 263 | } 264 | } catch (e) { 265 | console.log(`GBP command failed: ${e}`) 266 | return null 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/commands/help.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the help command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | 7 | import { msgDeleteTime, prefix } from '../config' 8 | import { capitalize } from '../common' 9 | import colours from '../colours' 10 | 11 | export default class TagCommand extends Command { 12 | constructor () { 13 | super('help', { 14 | aliases: ['help'], 15 | category: 'assistance', 16 | description: 'Sends a list of commands to be used with the bot.', 17 | args: [ 18 | { 19 | id: 'member', 20 | type: 'user' 21 | } 22 | ], 23 | cooldown: 1000 * msgDeleteTime, 24 | ratelimit: 1 25 | }) 26 | } 27 | 28 | // noinspection JSMethodCanBeStatic 29 | async exec (message, args) { 30 | try { 31 | // Set fields to be the commands available. 32 | const fields = [] 33 | for (const module of message.client.commandHandler.modules) { 34 | const name = capitalize(module[0]) 35 | const command = module[1] 36 | 37 | const category = capitalize(command.category.id) 38 | 39 | const args = [] 40 | 41 | for (const arg of command.args) { 42 | args.push(`${arg.id}`) 43 | } 44 | 45 | const usage = args.length === 0 46 | ? `\`${prefix}${command.aliases[0]}\`` 47 | : `\`${prefix}${command.aliases[0]} <${args.join('> <')}>\`` 48 | 49 | fields.push({ 50 | name, 51 | value: `Usage: ${usage}\nCategory: ${category}\n${command.description}` 52 | }) 53 | } 54 | if (fields.length === 0) { 55 | fields.push({ 56 | name: 'No Commands', 57 | value: 'There are currently no commands to use.' 58 | }) 59 | } 60 | 61 | const user = message.member ? message.member.user : message.author 62 | 63 | const embed = { 64 | title: 'List of Commands', 65 | color: colours.blue, 66 | fields, 67 | author: { 68 | name: user.username, 69 | icon_url: user.avatarURL 70 | } 71 | } 72 | 73 | if (message.channel.type !== 'dm') { 74 | await message.delete(1) 75 | if (!args.member) { 76 | const sent = await message.util.send({ embed }) 77 | return setTimeout(() => { 78 | sent.delete(1) 79 | }, msgDeleteTime * 1000) 80 | } else { 81 | const member = message.guild.member(args.member.id) 82 | const dm = await member.createDM() 83 | return dm.send({ embed }) 84 | } 85 | } else { 86 | return message.util.send({ embed }) 87 | } 88 | } catch (e) { 89 | console.log(`Help command failed: ${e}`) 90 | return null 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/commands/lmgtfy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the lmgtfy command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import colours from '../colours' 7 | import { errorMessage } from '../common' 8 | 9 | export default class LMGTFYCommand extends Command { 10 | constructor () { 11 | super('lmgtfy', { 12 | aliases: ['lmgtfy'], 13 | category: 'assistance', 14 | description: 'Sends a LMGTFY link with the specified query.', 15 | args: [ 16 | { 17 | id: 'query', 18 | match: 'rest' 19 | } 20 | ] 21 | }) 22 | } 23 | 24 | // noinspection JSMethodCanBeStatic 25 | async exec (message, args) { 26 | try { 27 | if (!args.query) { 28 | await message.react('❌') 29 | return message.util.send({ 30 | embed: errorMessage( 31 | 'No Query Specified', 'You need to specify a query.' 32 | ) 33 | }) 34 | } 35 | await message.delete(1) 36 | return message.util.send({ 37 | embed: { 38 | title: 'LMGTFY', 39 | color: colours.blue, 40 | url: `https://lmgtfy.com/?q=${args.query.replace(/\s/g, '+')}`, 41 | description: 'Here you go!', 42 | author: { 43 | name: message.member.user.username, 44 | icon_url: message.member.user.avatarURL 45 | } 46 | } 47 | }) 48 | } catch (e) { 49 | console.log(`Error sending initial message: ${e}`) 50 | return null 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/commands/mute.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the mute command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | 7 | import colours from '../colours' 8 | import { errorMessage } from '../common' 9 | import { mutedRole } from '../config' 10 | 11 | export default class RoleCommand extends Command { 12 | constructor () { 13 | super('mute', { 14 | aliases: ['mute'], 15 | category: 'moderator', 16 | description: 'Adds muted role to user.', 17 | args: [ 18 | { 19 | id: 'member', 20 | type: 'member' 21 | } 22 | ], 23 | clientPermissions: ['KICK_MEMBERS'], 24 | userPermissions: ['KICK_MEMBERS'], 25 | channelRestriction: 'guild' 26 | }) 27 | } 28 | 29 | // noinspection JSMethodCanBeStatic 30 | async exec (message, args) { 31 | try { 32 | const guild = message.guild 33 | if (!args.member) { 34 | await message.react('❌') 35 | const embed = errorMessage('No Member Specified', 'You need to' + 36 | ' specify a member to mute.') 37 | return message.util.send({ embed }) 38 | } 39 | const role = guild.roles.find(r => r.name === mutedRole) 40 | if (role === null) { 41 | await message.react('❌') 42 | const embed = errorMessage('Role Doesn\'t Exist', 'That role does' + 43 | ' not exist. Add a valid muted role.') 44 | return message.util.send({ embed }) 45 | } 46 | try { 47 | await args.member.addRole(role) 48 | await message.delete(1) 49 | return message.util.send({ 50 | embed: { 51 | title: 'Member Muted', 52 | color: colours.orange, 53 | description: `Muted ${args.member.user.tag}`, 54 | author: { 55 | name: message.member.user.username, 56 | icon_url: message.member.user.avatarURL 57 | } 58 | } 59 | }) 60 | } catch (e) { 61 | await message.react('❌') 62 | return message.util.send({ 63 | embed: errorMessage( 64 | 'Invalid Permission', 'Cannot mute that user.' 65 | ) 66 | }) 67 | } 68 | } catch (e) { 69 | console.log(`Error muting user: ${e}`) 70 | return null 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/commands/ping.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the ping command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import colours from '../colours' 7 | 8 | export default class PingCommand extends Command { 9 | constructor () { 10 | super('ping', { 11 | aliases: ['ping'], 12 | category: 'util', 13 | description: 'Shows ping and round trip time for the bot.' 14 | }) 15 | } 16 | 17 | async exec (message) { 18 | const embed = { 19 | title: 'Pong!', 20 | color: colours.blue 21 | } 22 | try { 23 | const sent = await message.util.send({ embed }) 24 | try { 25 | // Calculate difference in time between when message was send & when 26 | // it was edited. 27 | const timeDiff = (sent.editedAt || sent.createdAt) - 28 | (message.editedAt || message.createdAt) 29 | embed.fields = [ 30 | { 31 | name: 'Round Trip Time:', 32 | value: `${timeDiff}ms.` 33 | }, 34 | { 35 | name: 'Ping:', 36 | value: `${Math.round(this.client.ping)}ms.` 37 | } 38 | ] 39 | return message.util.send({ embed }) 40 | } catch (e) { 41 | console.log(`Error updating message: ${e}`) 42 | return null 43 | } 44 | } catch (e) { 45 | console.log(`Error sending initial message: ${e}`) 46 | return null 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/commands/prune.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the prune command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | 7 | export default class PruneCommand extends Command { 8 | constructor () { 9 | super('prune', { 10 | aliases: ['prune'], 11 | category: 'util', 12 | description: 'Deletes last x messages.', 13 | args: [ 14 | { 15 | id: 'amount', 16 | type: 'integer', 17 | default: 5 18 | } 19 | ], 20 | clientPermissions: ['MANAGE_MESSAGES'], 21 | userPermissions: ['MANAGE_MESSAGES'] 22 | }) 23 | } 24 | 25 | // noinspection JSMethodCanBeStatic 26 | async exec (message, args) { 27 | try { 28 | const amount = args.amount > 50 ? 50 : args.amount 29 | await message.delete(1) 30 | const messages = await message.channel.fetchMessages({ limit: amount }) 31 | return messages.deleteAll() 32 | } catch (e) { 33 | console.log(`Error deleting messages: ${e}`) 34 | return null 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/report.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the report command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import colours from '../colours' 7 | 8 | import { errorMessage } from '../common' 9 | import { channels } from '../config' 10 | 11 | export default class ReportCommand extends Command { 12 | constructor () { 13 | super('report', { 14 | aliases: ['report'], 15 | category: 'moderation', 16 | description: 'Sends a report to the #reports channel.', 17 | args: [ 18 | { 19 | id: 'message', 20 | match: 'rest' 21 | } 22 | ], 23 | channelRestriction: 'guild' 24 | }) 25 | } 26 | 27 | // noinspection JSMethodCanBeStatic 28 | async exec (message, args) { 29 | try { 30 | if (!args.message) { 31 | await message.react('❌') 32 | const embed = errorMessage('Message Not Valid', 33 | 'Please specify a message.') 34 | return message.util.send({ embed }) 35 | } 36 | await message.delete(1) 37 | 38 | return message.guild.channels.find(c => c.name === channels.report).send({ 39 | embed: { 40 | color: colours.red, 41 | title: 'New Report', 42 | description: args.message, 43 | author: { 44 | name: message.member.user.username, 45 | icon_url: message.member.user.avatarURL 46 | }, 47 | fields: [ 48 | { 49 | name: 'Channel:', 50 | value: message.channel.toString(), 51 | inline: true 52 | }, 53 | { 54 | name: 'Member:', 55 | value: message.member.toString(), 56 | inline: true 57 | } 58 | ], 59 | footer: { 60 | icon_url: message.member.user.avatarURL, 61 | text: `${message.member.user.tag} reported from ${message.channel.name}.` 62 | }, 63 | timestamp: new Date() 64 | } 65 | }) 66 | } catch (e) { 67 | console.log(`Report command failed: ${e}`) 68 | return null 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/commands/role.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the role command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | 7 | import colours from '../colours' 8 | import { allRoles } from '../approvedRoles' 9 | import { errorMessage } from '../common' 10 | 11 | export default class RoleCommand extends Command { 12 | constructor () { 13 | super('role', { 14 | aliases: ['role'], 15 | category: 'util', 16 | description: 'Adds or removed roles from a user.', 17 | args: [ 18 | { 19 | id: 'command' 20 | }, 21 | { 22 | id: 'role', 23 | match: 'rest' 24 | } 25 | ] 26 | }) 27 | } 28 | 29 | // noinspection JSMethodCanBeStatic 30 | async exec (message, args) { 31 | try { 32 | const guild = message.guild 33 | if (!args.command) { 34 | await message.react('❌') 35 | const embed = errorMessage('No Command Specified', 'You need to' + 36 | ' specify a command to use.') 37 | return message.util.send({ embed }) 38 | } 39 | if (!args.role) { 40 | await message.react('❌') 41 | const embed = errorMessage('No Role Specified', 'You need to specify' + 42 | ' a role to use.') 43 | return message.util.send({ embed }) 44 | } 45 | args.role = args.role.toLowerCase() 46 | if (!Object.keys(allRoles).includes(args.role)) { 47 | await message.react('❌') 48 | const embed = errorMessage('Invalid Role', 'You are not allowed to' + 49 | ' add or remove that role.') 50 | return message.util.send({ embed }) 51 | } 52 | const role = guild.roles.find(r => r.name === allRoles[args.role]) 53 | if (role === null) { 54 | await message.react('❌') 55 | const embed = errorMessage('Role Doesn\'t Exist', 'That role does' + 56 | ' not exist. Specify a valid role.') 57 | return message.util.send({ embed }) 58 | } 59 | switch (args.command) { 60 | case 'add': 61 | try { 62 | await message.member.addRole(role) 63 | await message.delete(1) 64 | return message.util.send({ 65 | embed: { 66 | title: 'Role Added', 67 | color: colours.green, 68 | description: `Added ${allRoles[args.role]} to ${message.member.user.tag}`, 69 | author: { 70 | name: message.member.user.username, 71 | icon_url: message.member.user.avatarURL 72 | } 73 | } 74 | }) 75 | } catch (e) { 76 | await message.react('❌') 77 | return message.util.send({ 78 | embed: errorMessage( 79 | 'Invalid Permission', 'Cannot add roles to that user.' 80 | ) 81 | }) 82 | } 83 | case 'rm': 84 | try { 85 | await message.member.removeRole(role) 86 | await message.delete(1) 87 | return message.util.send({ 88 | embed: { 89 | title: 'Role Removed', 90 | color: colours.red, 91 | description: `Removed ${args.role} from ${message.member.user.tag}`, 92 | author: { 93 | name: message.member.user.username, 94 | icon_url: message.member.user.avatarURL 95 | } 96 | } 97 | }) 98 | } catch (e) { 99 | await message.react('❌') 100 | return message.util.send({ 101 | embed: errorMessage( 102 | 'Invalid Permission', 'Cannot remove roles from that user.' 103 | ) 104 | }) 105 | } 106 | default: 107 | await message.react('❌') 108 | return message.util.send( 109 | { embed: errorMessage('Wrong Usage', 'That is not a role command.') } 110 | ) 111 | } 112 | } catch (e) { 113 | console.log(`Error sending role message: ${e}`) 114 | return null 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/commands/roles.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the roles command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | 7 | import colours from '../colours' 8 | import { msgDeleteTime, prefix } from '../config' 9 | import { allRoles } from '../approvedRoles' 10 | 11 | export default class RolesCommand extends Command { 12 | constructor () { 13 | super('roles', { 14 | aliases: ['roles'], 15 | category: 'util', 16 | description: 'Shows a list of self-assignable roles.', 17 | cooldown: 1000 * msgDeleteTime, 18 | ratelimit: 1 19 | }) 20 | } 21 | 22 | // noinspection JSMethodCanBeStatic 23 | async exec (message) { 24 | try { 25 | await message.delete(1) 26 | const guild = message.guild 27 | const embed = { 28 | title: 'Available Roles', 29 | color: colours.blue, 30 | author: { 31 | name: message.member.user.username, 32 | icon_url: message.member.user.avatarURL 33 | }, 34 | fields: [] 35 | } 36 | for (const role of guild.roles.array()) { 37 | if (Object.values(allRoles).includes(role.name)) { 38 | embed.fields.push({ 39 | name: role.name, 40 | value: `\`${prefix}role add ${role.name}\` | \`${prefix}role rm ${role.name}\`` 41 | }) 42 | } 43 | } 44 | if (embed.fields.length <= 0) { 45 | embed.color = colours.red 46 | embed.fields = [ 47 | { 48 | name: 'No (Approved) Roles', 49 | value: 'There are currently either no approved roles or no roles' + 50 | ' at all on this server.' 51 | } 52 | ] 53 | } 54 | const sent = await message.util.send({ embed }) 55 | return setTimeout(() => { 56 | sent.delete(1) 57 | }, msgDeleteTime * 1000) 58 | } catch (e) { 59 | console.log(`Error sending roles message: ${e}`) 60 | return null 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/commands/stats.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the stats command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import moment from 'moment' 7 | 8 | import colours from '../colours' 9 | 10 | export default class StatsCommand extends Command { 11 | constructor () { 12 | super('stats', { 13 | aliases: ['stats', 'statistics'], 14 | category: 'util', 15 | description: 'Shows some statistics about the server.' 16 | }) 17 | } 18 | 19 | // noinspection JSMethodCanBeStatic 20 | async exec (message) { 21 | try { 22 | const guild = message.guild 23 | const client = message.client 24 | const uptime = moment.duration(client.uptime) 25 | await message.delete(1) 26 | return message.util.send({ 27 | embed: { 28 | title: 'Server Stats', 29 | color: colours.blue, 30 | author: { 31 | name: message.member.user.username, 32 | icon_url: message.member.user.avatarURL 33 | }, 34 | fields: [ 35 | { 36 | name: guild.name, 37 | value: `Members: ${guild.memberCount}\n` + 38 | `Server was created at: ${moment(guild.createdAt) 39 | .format('YYYY/M/D')}\n` + 40 | `Num. of channels: ${guild.channels.array() 41 | .filter(channel => channel.type !== 'category').length}\n` + 42 | `Region: ${guild.region}\n` + 43 | `AFK Timeout: ${guild.afkTimeout}s\n` 44 | }, 45 | { 46 | name: 'Bot Information', 47 | value: `Uptime: ${uptime.hours()} hours, ${uptime.minutes()} mins, ${uptime.seconds()}s` 48 | } 49 | ] 50 | } 51 | }) 52 | } catch (e) { 53 | console.log(`Error sending stats message: ${e}`) 54 | return null 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/commands/tag.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the tag command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | 7 | import { errorMessage } from '../common' 8 | import tags from '../tags' 9 | 10 | export default class TagCommand extends Command { 11 | constructor () { 12 | super('tag', { 13 | aliases: ['tag'], 14 | category: 'assistance', 15 | description: 'Sends a preset text when called.', 16 | args: [ 17 | { 18 | id: 'tag' 19 | }, 20 | { 21 | id: 'member', 22 | type: 'user' 23 | } 24 | ] 25 | }) 26 | } 27 | 28 | // noinspection JSMethodCanBeStatic 29 | async exec (message, args) { 30 | try { 31 | let embed 32 | if (tags.hasOwnProperty(args.tag.toLowerCase())) { 33 | embed = tags[args.tag.toLowerCase()] 34 | if (message.channel.type !== 'dm') { 35 | await message.delete(1) 36 | } 37 | } else { 38 | embed = errorMessage('Tag Not Found', 'No tag with that name exists.') 39 | await message.react('❌') 40 | } 41 | const user = message.member ? message.member.user : message.author 42 | embed.author = { 43 | name: user.username, 44 | icon_url: user.avatarURL 45 | } 46 | if (message.channel.type === 'dm') { 47 | return message.util.send({ embed }) 48 | } else if (args.member) { 49 | return message.util.send(args.member, { embed }) 50 | } else { 51 | return message.util.send({ embed }) 52 | } 53 | } catch (e) { 54 | console.log(`Tag command failed: ${e}`) 55 | return null 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/commands/tags.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the tags command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import { capitalize } from '../common' 7 | 8 | import { msgDeleteTime, prefix } from '../config' 9 | import colours from '../colours' 10 | 11 | import tags from '../tags' 12 | 13 | export default class TagCommand extends Command { 14 | constructor () { 15 | super('tags', { 16 | aliases: ['tags', 'taglist'], 17 | category: 'assistance', 18 | description: 'List of Available Tags.', 19 | cooldown: 1000 * msgDeleteTime, 20 | ratelimit: 1 21 | }) 22 | } 23 | 24 | // noinspection JSMethodCanBeStatic 25 | async exec (message) { 26 | try { 27 | const fields = [] 28 | for (const [key, val] of Object.entries(tags)) { 29 | const name = val.title ? val.title : capitalize(key) 30 | fields.push({ name, value: `${prefix}tag ${key}` }) 31 | } 32 | if (fields.length === 0) { 33 | fields.push({ 34 | name: 'No Tags', 35 | value: 'There are currently no tags added.' 36 | }) 37 | } 38 | const user = message.member ? message.member.user : message.author 39 | const embed = { 40 | title: 'List of Tags', 41 | color: colours.blue, 42 | fields, 43 | author: { 44 | name: user.username, 45 | icon_url: user.avatarURL 46 | } 47 | } 48 | if (message.channel.type !== 'dm') { 49 | await message.delete(1) 50 | const sent = await message.util.send({ embed }) 51 | return setTimeout(() => { 52 | sent.delete(1) 53 | }, msgDeleteTime * 1000) 54 | } else { 55 | return message.util.send({ embed }) 56 | } 57 | } catch (e) { 58 | console.log(`Tags command failed: ${e}`) 59 | return null 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/commands/unban.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the unban command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import Database from 'better-sqlite3' 7 | import colours from '../colours' 8 | 9 | import { errorMessage } from '../common' 10 | import { channels, dbFile } from '../config' 11 | 12 | export default class UnbanCommand extends Command { 13 | constructor () { 14 | super('unban', { 15 | aliases: ['unban'], 16 | category: 'moderation', 17 | description: 'Unbans a user.', 18 | args: [ 19 | { 20 | id: 'member' 21 | }, 22 | { 23 | id: 'reason', 24 | match: 'rest', 25 | default: 'Unbanned via unban command.' 26 | } 27 | ], 28 | clientPermissions: ['BAN_MEMBERS'], 29 | userPermissions: ['BAN_MEMBERS'], 30 | channelRestriction: 'guild' 31 | }) 32 | } 33 | 34 | // noinspection JSMethodCanBeStatic 35 | async exec (message, args) { 36 | try { 37 | if (!args.member) { 38 | await message.react('❌') 39 | const embed = errorMessage('Member Not Found', 40 | 'No member found with that name.') 41 | return message.util.send({ embed }) 42 | } 43 | try { 44 | let user 45 | const bannedUsers = await message.guild.fetchBans() 46 | // Check for user by tag, username, or discrim in banned users list. 47 | if (bannedUsers.find(t => t.tag === args.member) !== null) { 48 | user = bannedUsers.find(t => t.tag === args.member) 49 | } else if (bannedUsers.find(u => u.username === args.member) !== null) { 50 | user = bannedUsers.find(u => u.username === args.member) 51 | } else if (bannedUsers.get(args.member) !== null) { 52 | user = bannedUsers.get(args.member) 53 | } 54 | if (user === null || user === undefined) { 55 | await message.react('❌') 56 | const embed = errorMessage('User not Found', 'That user has not' + 57 | ' been banned.') 58 | return message.util.send({ embed }) 59 | } 60 | await message.delete(1) 61 | try { 62 | const db = await Database(dbFile) 63 | db.prepare('DELETE FROM bans WHERE discord_id = ?').run(user.id) 64 | 65 | try { 66 | await message.guild.unban(user, args.reason) 67 | await user.send({ 68 | embed: { 69 | title: `You have been unbanned from ${message.guild.name}.`, 70 | color: colours.green, 71 | thumbnail: { 72 | url: message.guild.iconURL 73 | }, 74 | fields: [ 75 | { 76 | name: 'Reason:', 77 | value: args.reason 78 | }], 79 | footer: { 80 | text: `You can now join back with an invite link.` 81 | } 82 | } 83 | }) 84 | return message.guild.channels.find(c => c.name === channels.ban).send({ 85 | embed: { 86 | color: colours.green, 87 | title: 'Unban', 88 | description: `${user} has been Unbanned`, 89 | author: { 90 | name: message.member.user.username, 91 | icon_url: message.member.user.avatarURL 92 | }, 93 | fields: [ 94 | { 95 | name: 'Reason:', 96 | value: args.reason 97 | }], 98 | footer: { 99 | text: `${user.tag} is able to join the server again.` 100 | }, 101 | timestamp: new Date() 102 | } 103 | }) 104 | } catch (e) { 105 | console.log(`Error unbanning user: ${e}`) 106 | } 107 | } catch (e) { 108 | console.log(`Error deleting user from database: ${e}`) 109 | } 110 | } catch (e) { 111 | console.log(`Couldn't fetch user from banned: ${e}`) 112 | } 113 | } catch (e) { 114 | console.log(`Ban command failed: ${e}`) 115 | return null 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/commands/unmute.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the unmute command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | 7 | import colours from '../colours' 8 | import { errorMessage } from '../common' 9 | import { mutedRole } from '../config' 10 | 11 | export default class RoleCommand extends Command { 12 | constructor () { 13 | super('unmute', { 14 | aliases: ['unmute'], 15 | category: 'moderator', 16 | description: 'Removes muted role to user.', 17 | args: [ 18 | { 19 | id: 'member', 20 | type: 'member' 21 | } 22 | ], 23 | clientPermissions: ['KICK_MEMBERS'], 24 | userPermissions: ['KICK_MEMBERS'], 25 | channelRestriction: 'guild' 26 | }) 27 | } 28 | 29 | // noinspection JSMethodCanBeStatic 30 | async exec (message, args) { 31 | try { 32 | const guild = message.guild 33 | if (!args.member) { 34 | await message.react('❌') 35 | const embed = errorMessage('No Member Specified', 'You need to' + 36 | ' specify a member to unmute.') 37 | return message.util.send({ embed }) 38 | } 39 | const role = guild.roles.find(r => r.name === mutedRole) 40 | if (role === null) { 41 | await message.react('❌') 42 | const embed = errorMessage('Role Doesn\'t Exist', 'That role does' + 43 | ' not exist. Add a valid muted role.') 44 | return message.util.send({ embed }) 45 | } 46 | try { 47 | await args.member.removeRole(role) 48 | await message.delete(1) 49 | return message.util.send({ 50 | embed: { 51 | title: 'Member Unmuted', 52 | color: colours.orange, 53 | description: `Unmuted ${args.member.user.tag}`, 54 | author: { 55 | name: message.member.user.username, 56 | icon_url: message.member.user.avatarURL 57 | } 58 | } 59 | }) 60 | } catch (e) { 61 | await message.react('❌') 62 | return message.util.send({ 63 | embed: errorMessage( 64 | 'Invalid Permission', 'Cannot unmute that user.' 65 | ) 66 | }) 67 | } 68 | } catch (e) { 69 | console.log(`Error unmuting user: ${e}`) 70 | return null 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/commands/users.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the users command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | 7 | import colours from '../colours' 8 | 9 | export default class UsersCommand extends Command { 10 | constructor () { 11 | super('users', { 12 | aliases: ['users'], 13 | category: 'util', 14 | description: 'Shows how many users are on the server.' 15 | }) 16 | } 17 | 18 | // noinspection JSMethodCanBeStatic 19 | async exec (message, args) { 20 | try { 21 | await message.delete(1) 22 | const guild = message.guild 23 | return message.util.send({ 24 | embed: { 25 | title: 'Users', 26 | color: colours.blue, 27 | description: `There are currently ${guild.memberCount 28 | } users in this discord. (${guild.members.array().filter( 29 | m => m.presence.status !== 'offline').length} currently online).`, 30 | author: { 31 | name: message.member.user.username, 32 | icon_url: message.member.user.avatarURL 33 | } 34 | } 35 | }) 36 | } catch (e) { 37 | console.log(`Error sending users message: ${e}`) 38 | return null 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/warn.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the warn command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import Database from 'better-sqlite3' 7 | import colours from '../colours' 8 | 9 | import { errorMessage } from '../common' 10 | import { 11 | autoBan, 12 | autoBanWarns, 13 | banMsgDelete, 14 | channels, 15 | dbFile 16 | } from '../config' 17 | 18 | export default class WarnCommand extends Command { 19 | constructor () { 20 | super('warn', { 21 | aliases: ['warn'], 22 | category: 'moderation', 23 | description: 24 | 'Warns a user, and bans them if the maximum warns has' + 25 | ' been exceeded', 26 | args: [ 27 | { 28 | id: 'member', 29 | type: 'member' 30 | }, 31 | { 32 | id: 'reason', 33 | match: 'rest' 34 | } 35 | ], 36 | clientPermissions: ['KICK_MEMBERS'], 37 | userPermissions: ['KICK_MEMBERS'], 38 | channelRestriction: 'guild' 39 | }) 40 | } 41 | 42 | // noinspection JSMethodCanBeStatic 43 | async exec (message, args) { 44 | try { 45 | if (!args.member) { 46 | await message.react('❌') 47 | const embed = errorMessage( 48 | 'No Member Specified', 49 | "You didn't" + ' specify a member.' 50 | ) 51 | return message.util.send({ embed }) 52 | } 53 | if (!args.reason) { 54 | await message.react('❌') 55 | const embed = errorMessage( 56 | 'Reason Not Specified', 57 | 'Please specify a' + ' reason for the warning.' 58 | ) 59 | return message.util.send({ embed }) 60 | } 61 | if (args.member.permissions.has('KICK_MEMBERS')) { 62 | await message.react('❌') 63 | const embed = errorMessage( 64 | "Can't Warn Member", 65 | 'You are not allowed' + ' to warn that member.' 66 | ) 67 | return message.util.send({ embed }) 68 | } 69 | 70 | await message.delete(1) 71 | 72 | const user = args.member.user 73 | const reason = args.reason 74 | const executor = message.member.user 75 | 76 | try { 77 | // Add warning into database & check numbers of previous warnings. 78 | // Ban user if configs says to do so & warns is over limit. 79 | const db = new Database(dbFile) 80 | await db 81 | .prepare( 82 | 'INSERT INTO warnings (discord_id, reason, date, mod_id) VALUES (?, ?, CURRENT_TIMESTAMP, ?)' 83 | ) 84 | .run(user.id, reason, executor.id) 85 | 86 | const row = db 87 | .prepare( 88 | 'SELECT COUNT(*) AS count FROM warnings WHERE discord_id = ?' 89 | ) 90 | .get(user.id) 91 | 92 | let color = colours.yellow 93 | if (row.count === 2) { 94 | color = colours.orange 95 | } else if (row.count >= 3) { 96 | color = colours.red 97 | } 98 | await message.guild.channels.find(c => c.name === channels.warn).send({ 99 | embed: { 100 | title: `Warning #${row.count}.`, 101 | color, 102 | author: { 103 | name: executor.username, 104 | icon_url: executor.avatarURL 105 | }, 106 | description: `${user} has been warned for ${reason}.`, 107 | footer: { 108 | icon_url: user.avatarURL, 109 | text: `${user.tag} has been warned.` 110 | } 111 | } 112 | }) 113 | await user.send({ 114 | embed: { 115 | title: `You have received a warning on ${message.guild.name}.`, 116 | color, 117 | thumbnail: { 118 | url: message.guild.iconURL 119 | }, 120 | fields: [ 121 | { 122 | name: 'Reason:', 123 | value: reason 124 | } 125 | ] 126 | } 127 | }) 128 | if (autoBan && row.count >= autoBanWarns) { 129 | await user.send({ 130 | embed: { 131 | title: `You have been banned from ${message.guild.name}.`, 132 | color, 133 | thumbnail: { 134 | url: message.guild.iconURL 135 | }, 136 | fields: [ 137 | { 138 | name: `Reason: ${reason} (${row.count} warnings).`, 139 | value: `Your messages from the past ${banMsgDelete} days have been deleted.` 140 | } 141 | ] 142 | } 143 | }) 144 | await args.member.ban({ 145 | days: banMsgDelete, 146 | reason: `${row.count} warnings! Most recent: ${reason}.` 147 | }) 148 | await db 149 | .prepare('DELETE FROM warnings WHERE discord_id = ?') 150 | .run(user.id) 151 | await message.guild.channels.find(c => c.name === channels.ban).send({ 152 | embed: { 153 | title: 'Auto Ban', 154 | color, 155 | description: `${user} has been banned. (Maximum warnings exceeded (${autoBanWarns}))`, 156 | author: { 157 | name: message.client.user.username, 158 | icon_url: message.client.user.avatarURL 159 | }, 160 | fields: [ 161 | { 162 | name: 'Reason:', 163 | value: reason 164 | } 165 | ], 166 | footer: { 167 | icon_url: user.avatarURL, 168 | text: `${ 169 | user.tag 170 | } banned. Messages from past ${banMsgDelete} days deleted.` 171 | }, 172 | timestamp: new Date() 173 | } 174 | }) 175 | } 176 | } catch (e) { 177 | console.log(`Error accessing database: ${e}`) 178 | } 179 | } catch (e) { 180 | console.log(`Warn command failed: ${e}`) 181 | return null 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/commands/warnList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * The file that handles the warnlist command. 4 | */ 5 | import { Command } from 'discord-akairo' 6 | import Database from 'better-sqlite3' 7 | import colours from '../colours' 8 | 9 | import { errorMessage } from '../common' 10 | import { dbFile } from '../config' 11 | 12 | export default class ListWarnsCommand extends Command { 13 | constructor () { 14 | super('warnlist', { 15 | aliases: ['warnlist', 'warns'], 16 | category: 'moderation', 17 | description: 'Lists warnings of a user.', 18 | args: [ 19 | { 20 | id: 'member', 21 | type: 'member' 22 | } 23 | ], 24 | clientPermissions: ['KICK_MEMBERS'], 25 | userPermissions: ['KICK_MEMBERS'], 26 | channelRestriction: 'guild' 27 | }) 28 | } 29 | 30 | // noinspection JSMethodCanBeStatic 31 | async exec (message, args) { 32 | try { 33 | if (!args.member) { 34 | await message.react('❌') 35 | const embed = errorMessage( 36 | 'Member Not Found', 37 | 'No member found with' + ' that name.' 38 | ) 39 | return message.util.send({ embed }) 40 | } 41 | 42 | await message.delete(1) 43 | 44 | try { 45 | const user = args.member.user 46 | const db = new Database(dbFile) 47 | 48 | const embed = { 49 | title: `Warnings for ${user.tag}`, 50 | color: colours.blue, 51 | fields: [], 52 | author: { 53 | name: message.member.user.username, 54 | icon_url: message.member.user.avatarURL 55 | } 56 | } 57 | let count = 0 58 | let warnings = [] 59 | 60 | const row = db 61 | .prepare('SELECT * FROM warnings WHERE discord_id = ?') 62 | .get(user.id) 63 | 64 | if (row) { 65 | warnings.push(row) 66 | count++ 67 | } 68 | 69 | if (count <= 0) { 70 | embed.fields.push({ 71 | name: `Warnings: ${count}`, 72 | value: 'No warnings found' 73 | }) 74 | } else { 75 | for (let i = 0; i < count; ++i) { 76 | const warning = warnings[i] 77 | const date = new Date(warning.date) 78 | const day = '0' + date.getDate() 79 | const month = '0' + (date.getMonth() + 1) 80 | const year = date.getFullYear() 81 | 82 | const mod = message.guild.member(warning.mod_id) 83 | const modName = 84 | mod == null ? 'unknown (' + warning.mod_id + ')' : mod.user.tag 85 | 86 | embed.fields.push({ 87 | name: `Warning ${i + 1} (${day.substr(-2)}.${month.substr( 88 | -2 89 | )}.${year})`, 90 | value: `"${warning.reason}" by ${modName}` 91 | }) 92 | } 93 | } 94 | return message.util.send({ embed }) 95 | } catch (e) { 96 | console.log(`Error accessing database: ${e}`) 97 | } 98 | } catch (e) { 99 | console.log(`Warnlist command failed: ${e}`) 100 | return null 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * Common functions in one place. 4 | */ 5 | 6 | import colours from './colours' 7 | 8 | export const capitalize = word => `${word[0].toUpperCase()}${word.slice(1)}` 9 | 10 | export const errorMessage = (title, description) => { 11 | return { 12 | title, 13 | color: colours.red, 14 | description 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * Takes config from environment & put it in one place. 4 | */ 5 | import dotenv from 'dotenv' 6 | import { join } from 'path' 7 | 8 | dotenv.config() 9 | 10 | export const botToken = process.env.BOT_TOKEN 11 | export const ownerID = process.env.OWNER_ID 12 | export const prefix = process.env.PREFIX || '.' 13 | export const msgDeleteTime = process.env.MSG_DELETE_TIME || 10 14 | export const dbFile = join( 15 | __dirname, 16 | '..', 17 | process.env.DB_FILE || 'devmod.sqlite' 18 | ) 19 | export const migrationDir = join(__dirname, '..', 'migrations') 20 | export const autoBan = process.env.AUTOBAN || true 21 | export const autoBanWarns = process.env.AUTOBAN_WARNS || 3 22 | export const banMsgDelete = process.env.BAN_MSG_DELETE || 0 23 | export const channels = { 24 | warn: process.env.CHANNEL_LOG_WARN || 'warnings', 25 | ban: process.env.CHANNEL_LOG_BAN || 'bans', 26 | report: process.env.CHANNEL_REPORT || 'reports', 27 | roles: process.env.CHANNEL_ROLES || 'roles' 28 | } 29 | export const mutedRole = process.env.MUTED_ROLE || 'muted' 30 | export const pointEmoji = process.env.POINTS_EMOJI || 'boye' 31 | export const statusInterval = process.env.STATUS_INTERVAL || 5 32 | export const pointsTopCount = process.env.POINTS_TOP_COUNT || 10 33 | -------------------------------------------------------------------------------- /src/reactionRoles.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * File that handles adding roles based on reactions. 4 | */ 5 | import Database from 'better-sqlite3' 6 | 7 | import { allRolesMap } from './approvedRoles' 8 | import { dbFile } from './config' 9 | 10 | const roleAction = async ( 11 | { client, guildId, messageId, userId, emojiName }, 12 | remove = false 13 | ) => { 14 | const db = new Database(dbFile) 15 | const reactions = db.prepare( 16 | 'SELECT * FROM `settings` WHERE `key` = ? LIMIT 1' 17 | ) 18 | 19 | for (const row of reactions.iterate('reaction_message_ids')) { 20 | const guild = client.guilds.get(guildId) 21 | const member = guild.member(userId) 22 | const roles = guild.roles 23 | const messageIDs = JSON.parse(row.value) 24 | 25 | if (!messageIDs.includes(messageId)) continue 26 | 27 | for (const reaction of Object.keys(allRolesMap)) { 28 | const react = allRolesMap[reaction] 29 | 30 | if (emojiName !== react.emoji) continue 31 | const role = roles.find(r => r.name === reaction) 32 | if (role !== null) { 33 | remove ? await member.removeRole(role) : await member.addRole(role) 34 | } 35 | } 36 | } 37 | } 38 | 39 | export const roleAdd = async ( 40 | client, 41 | guildId, 42 | messageId, 43 | userId, 44 | emojiName 45 | ) => { 46 | try { 47 | const ctx = { 48 | client, 49 | guildId, 50 | messageId, 51 | userId, 52 | emojiName 53 | } 54 | await roleAction(ctx) 55 | } catch (e) { 56 | console.log(`Failed to add role: ${e}`) 57 | } 58 | } 59 | 60 | export const roleRm = async (client, guildId, messageId, userId, emojiName) => { 61 | try { 62 | const ctx = { 63 | client, 64 | guildId, 65 | messageId, 66 | userId, 67 | emojiName 68 | } 69 | await roleAction(ctx, true) 70 | } catch (e) { 71 | console.log(`Failed to remove role: ${e}`) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/sendRolesMessage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * File to send message and add reactions to specified channel. 4 | */ 5 | import discord from 'discord.js' 6 | import Database from 'better-sqlite3' 7 | 8 | import { rolesMessages } from './approvedRoles' 9 | import colours from './colours' 10 | import { botToken, channels, dbFile } from './config' 11 | 12 | const client = new discord.Client() 13 | 14 | client.on('ready', async () => { 15 | try { 16 | console.log(`Logged in as ${client.user.tag}.`) 17 | const guild = client.guilds.array()[0] 18 | const messageIDs = [] 19 | let count = 0 20 | rolesMessages.forEach(async roles => { 21 | const m = [] 22 | for (const reaction of Object.values(roles.roles)) { 23 | m.push(`${reaction.name}: ${reaction.emoji}`) 24 | } 25 | const message = await guild.channels.find(c => c.name === channels.roles).send({ 26 | embed: { 27 | title: roles.name, 28 | color: colours.blue, 29 | description: `${roles.message}\n\n${m.join('\n')}` 30 | } 31 | }) 32 | messageIDs.push(message.id) 33 | for (const reaction of Object.values(roles.roles)) { 34 | await message.react(reaction.emoji) 35 | } 36 | if (++count === rolesMessages.length) { 37 | finished(messageIDs) 38 | } 39 | }) 40 | } catch (e) { 41 | console.log(`Error sending message: ${e}`) 42 | } 43 | }) 44 | 45 | async function finished (messageIDs) { 46 | await client.destroy() 47 | const IDsString = JSON.stringify(messageIDs) 48 | console.log(IDsString) 49 | try { 50 | const db = new Database(dbFile) 51 | 52 | db.prepare( 53 | 'INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)' 54 | ).run('reaction_message_ids', IDsString) 55 | console.log('Success!') 56 | } catch (e) { 57 | console.log(`Error setting message ID in database: ${e}`) 58 | } 59 | } 60 | 61 | client.login(botToken) 62 | -------------------------------------------------------------------------------- /src/tags.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Gabe Dunn 2018 3 | * List of available tags. 4 | */ 5 | import colours from './colours' 6 | 7 | export default { 8 | bot: { 9 | title: 'Bot Channel', 10 | color: colours.blue, 11 | description: 'Please use commands related to the bot in the' + 12 | ' <#271293371438333952> channel. Thanks.' 13 | }, 14 | ask: { 15 | title: 'Asking to Ask', 16 | color: colours.red, 17 | description: 'Instead of asking to ask, ask your question instead.' + 18 | ' People can help you better if they know your question.\n\n' + 19 | 'Example: "Hey can anyone help me with some JS?" & "Anyone good with' + 20 | ' JS?" => "I\'m having trouble adding a class to a div using JS. Can I' + 21 | ' have some help?"' 22 | }, 23 | jobs: { 24 | title: 'Jobs', 25 | color: colours.blue, 26 | description: 'No job offers allowed on this server please. Free work is' + 27 | ' allowed, but not many people will want to work for free.' 28 | }, 29 | whynojobs: { 30 | title: 'Why there are no Jobs', 31 | color: colours.blue, 32 | description: 'In the past, there have been multiple cases of people' + 33 | ' being scammed when taking a job with someone else on this server. We' + 34 | ' do not want to be associated with that, so we have removed jobs' + 35 | ' entirely.\n\nThanks for understanding.' 36 | }, 37 | sfw: { 38 | title: 'SFW (Safe for Work)', 39 | color: colours.yellow, 40 | description: 'Please keep the messages safe for work here.' 41 | }, 42 | channels: { 43 | title: 'Posting in Multiple Channels', 44 | color: colours.orange, 45 | description: 'It would be greatly appreciated if you kept your posting' + 46 | ' to the channel designated for that topic. Additionally, please refrain' + 47 | ' from posting the same question in multiple channels.' 48 | }, 49 | cs: { 50 | title: 'Christian Server', 51 | color: colours.red, 52 | image: { url: 'https://cdn.discordapp.com/attachments/174075418410876928/377425219872096256/maxresdefault.png' } 53 | }, 54 | canudont: { 55 | title: 'Can U Dont', 56 | color: colours.blue, 57 | image: { url: 'https://cdn.discordapp.com/attachments/174075418410876928/428989988286365696/can_u_dont.jpg' } 58 | }, 59 | code: { 60 | title: 'Use Code Blocks', 61 | color: colours.blue, 62 | fields: [ 63 | { 64 | name: 'To directly post code into Discord, type:', 65 | value: '\\`\\`\\`lang\n// code\nconsole.log(\'I have no' + 66 | ' language.\')\n\\`\\`\\`' 67 | }, { 68 | name: 'For syntax highlighting replace lang with the language (js,' + 69 | ' css, html, etc.):', 70 | value: '\\`\\`\\`js\nconsole.log(\'hello this is js\')\n\\`\\`\\`' 71 | }, { 72 | name: 'How the first will look:', 73 | value: '```LANGUAGE\n// code\nconsole.log(\'I have no language.\')\n```' 74 | }, { 75 | name: 'How it will look with highlighting:', 76 | value: '```js\nconsole.log(\'hello this is js\')\n```' 77 | }] 78 | }, 79 | pen: { 80 | title: 'Post Your Code', 81 | color: colours.blue, 82 | description: 'Post your code online. Here are some options:', 83 | fields: [ 84 | { 85 | name: 'CodePen (https://codepen.io/)', 86 | value: 'CodePen is a nice place to post your code online.', 87 | inline: true 88 | }, { 89 | name: 'JSFiddle (https://jsfiddle.net/)', 90 | value: 'JSFiddle is also a nice place to post your code online.', 91 | inline: true 92 | }, 93 | { 94 | name: 'GitHub (https://github.com)', 95 | value: 'GitHub is a great place to post the code for full projects.', 96 | inline: true 97 | }, 98 | { 99 | name: 'CodeSandbox (https://codesandbox.io/)', 100 | value: 'CodeSandbox is a great place to play around with a full node' + 101 | ' environment without having it locally.', 102 | inline: true 103 | }] 104 | }, 105 | gbp: { 106 | title: 'Good Boye Points', 107 | color: colours.green, 108 | description: 'Good Boye Points (GBP) are the best way to tell if a user' + 109 | ' has been a good addition to the server. You can get GBP only by the' + 110 | ' generosity of the overlords (Admins & Mods). See how many you have with' + 111 | ' `.gbp show`. Find the best people on the server with `.gbp top`' 112 | }, 113 | roadmap: { 114 | title: 'Developer Roadmap', 115 | url: 'https://github.com/kamranahmedse/developer-roadmap', 116 | color: colours.blue, 117 | description: 'This is a very nice outline on the general technologies' + 118 | ' recommended for the path of your choosing. Use the as an outline, and' + 119 | ' not as the sole source of authority. Make your own decisions as well.' 120 | }, 121 | nice: { 122 | title: 'Nice', 123 | color: colours.blue, 124 | image: { url: 'https://cdn.discordapp.com/attachments/287200463382642689/433326230495035412/1447912710960.jpg' } 125 | }, 126 | editor: { 127 | title: 'IDEs & Text Editors', 128 | color: colours.blue, 129 | description: 'There are many different ways to edit code, from code' + 130 | ' editors to Integrated Development Environments ("IDEs"). Here are some' + 131 | ' differences between the two and some examples of each:', 132 | fields: [ 133 | { 134 | name: 'IDEs', 135 | value: 'IDEs (Integrated Development Environment) are programs that' + 136 | ' include a code editor, but also integrations with various other' + 137 | ' development tools (linters, version control,' + 138 | ' intellisense/autocomplete, automatic refactoring, database' + 139 | ' management, etc.).' 140 | }, 141 | { 142 | name: 'Code Editors', 143 | value: 'Code editors are text editors that usually include syntax' + 144 | ' highlighting, simple project management, and other helpful code' + 145 | ' editing tools.' 146 | }, 147 | { 148 | name: 'WebStorm/PHPStorm (or any other JetBrains Product)', 149 | value: 'These IDEs, as they have a full suite of tools for' + 150 | ' development. Additionally they have a plugin system for anything' + 151 | ' that they do not automatically include. [Webstorm Download](https://www.jetbrains.com/webstorm/), [PHPStorm Download](https://www.jetbrains.com/phpstorm/)' 152 | }, 153 | { 154 | name: 'Visual Studio', 155 | value: 'Visual studio is a full IDE made by microsoft. It works well' + 156 | ' with .NET based languages, as they are made by the same people.' + 157 | ' They also include a plugin system. [Download](https://visualstudio.microsoft.com/)' 158 | }, 159 | { 160 | name: 'NetBeans', 161 | value: 'I honestly don\'t know much about NetBeans, having never' + 162 | ' used it. If you know more make a PR on the DevMod repo. I do know' + 163 | ' that it is a Java IDE. [Download](https://netbeans.org/)' 164 | }, 165 | { 166 | name: 'Atom', 167 | value: 'Atom is a code editor based on web technology. It\'s made by' + 168 | ' GitHub, and has a massive community, with plugins for everything. [Download](https://atom.io/)' 169 | }, 170 | { 171 | name: 'VS Code', 172 | value: 'VS Code is another editor based off of web technology, but' + 173 | ' is better optimized and runs faster. This is built by microsoft' + 174 | ' and has a large set of plugins as well. [Download](https://code.visualstudio.com/)' 175 | }, 176 | { 177 | name: 'Sublime Text', 178 | value: 'Sublime text starts off as a nice small and fast editor.' + 179 | ' It\'s the fastest text editor that I\'ve seen. There is also a' + 180 | ' wide selection of plugins. [Download](https://www.sublimetext.com/)' 181 | }, 182 | { 183 | name: 'Vim', 184 | value: 'Vim is a command line text editor with plugins that can do' + 185 | ' pretty much anything. It is largely popular, but has a learning' + 186 | ' curve before you can be productive in it. [Download](https://www.vim.org/)' 187 | }, 188 | { 189 | name: 'Brackets', 190 | value: 'Brackets is also based on web tech, and has a live reload' + 191 | ' feature that allows you to view your website live. Many other' + 192 | ' editors have this feature, but few work as smoothly as this one. [Download](http://brackets.io/)' 193 | }, 194 | { 195 | name: 'Notepad++', 196 | value: 'Notepad++ is a very lightweight code editor, with a lot ' + 197 | 'of plugins for everything you can think of. It is beyond excellent ' + 198 | 'for quick edit or doodle work. [Download](https://notepad-plus-plus.org/)' 199 | } 200 | ] 201 | }, 202 | framework: { 203 | title: 'Frameworks (& Libraries)', 204 | color: colours.green, 205 | description: 'There is a large debate as to which framework is the best' + 206 | ' for your webapp, and this is just an overview of the top contenders.', 207 | fields: [ 208 | { 209 | name: 'Vue', 210 | value: 'Vue is a web framework that is easy to learn and use while' + 211 | ' being quite powerful. It has been described as taking the best' + 212 | ' from both React and Angular, and combining them. They have a large' + 213 | ' community, and it\'s quite fun to use. It has separation of' + 214 | ' concerns while being all in the same file, and has a large' + 215 | ' community of people and plugins. There are projects like ream and' + 216 | ' nuxt for SSR, and it\'s lightweight (smaller file than jQuery).' + 217 | ' Vue also has nativescript-vue and weex to write mobile apps using' + 218 | ' the same Vue syntax.' 219 | }, 220 | { 221 | name: 'React', 222 | value: 'React is not a framework, and is rather a library. It is' + 223 | ' backed by Facebook, and has a lot of useful features. It uses JSX,' + 224 | ' which is writing your HTML (or XML, actually) in javascript, and' + 225 | ' makes for a bit of a learning curve, but in the short time it' + 226 | ' takes to learn it\'s interesting to use. React also has React' + 227 | ' native which allows mobile development using the same syntax and' + 228 | ' application logic as your rect webapp.' 229 | }, 230 | { 231 | name: 'Angular 5', 232 | value: 'I do not know as much about Angular as I do Vue and React,' + 233 | ' (please make a PR if you have more knowledge), I do know that to' + 234 | ' use Angular, it\'s almost required to use typescript. Angular is a' + 235 | ' full MVC, so it provided the entire suite of tools including' + 236 | ' routing, state management, etc. This does lead to a more' + 237 | ' opinionated way of doing things, but makes making decisions a lot' + 238 | ' easier.' 239 | } 240 | ], 241 | footer: { 242 | text: 'My personal recommendation is Vue but definitely try out the' + 243 | ' others and use what you prefer.' 244 | } 245 | }, 246 | invite: { 247 | title: 'Invite Link', 248 | color: colours.blue, 249 | url: 'https://discord.me/devcord', 250 | description: 'You can invite people with' + 251 | ' this link: [https://discord.me/devcord](https://discord.me/devcord).' 252 | }, 253 | doubt: { 254 | title: '[x] Doubt', 255 | color: colours.blue, 256 | image: { url: 'https://media.discordapp.net/attachments/174075418410876928/435482310612221993/doubt.jpg?width=400&height=228' } 257 | }, 258 | fasy: { 259 | color: colours.blue, 260 | image: { url: 'https://media.discordapp.net/attachments/174075418410876928/435887256843321354/loamy.jpg?width=401&height=84' } 261 | }, 262 | dog: { 263 | color: colours.blue, 264 | image: { url: 'https://cdn.discordapp.com/attachments/174075418410876928/436958508039012379/unknown.png' } 265 | }, 266 | sqlinjection: { 267 | title: 'Bind your parameters to prevent SQL injection', 268 | color: colours.blue, 269 | description: 'Don\'t get hacked, use prepared statements as explained here:', 270 | fields: [ 271 | { 272 | name: 'PDO', 273 | value: '[Prepared statements with PDO](https://secure.php.net/manual/en/pdo.prepared-statements.php).' 274 | }, { 275 | name: 'Mysqli', 276 | value: '[Prepared statements with mysqli](https://secure.php.net/manual/en/mysqli.quickstart.prepared-statements.php).' 277 | } 278 | ] 279 | }, 280 | template: { 281 | title: 'Template', 282 | color: colours.blue, 283 | description: 'The information needed to post your project in this channel.', 284 | fields: [ 285 | { 286 | name: 'Project Description', 287 | value: 'A short description of the project.' 288 | }, 289 | { 290 | name: 'Time Commitment', 291 | value: 'How long will this project take? How much time can someone expect to commit to this project? Is there a deadline or desired date of completion?' 292 | }, 293 | { 294 | name: 'Languages', 295 | value: 'List all relevant languages.' 296 | }, 297 | { 298 | name: 'Skill Level', 299 | value: 'What skill level are you looking for? Are you willing to work with junior developers or prefer middle/senior level developers?' 300 | }, 301 | { 302 | name: 'Communication', 303 | value: 'What is the preferred method of communication? If a developer is interested, how should they contact you? Do NOT give out email addresses or other personally identifiable information.' 304 | } 305 | ] 306 | }, 307 | flex: { 308 | title: 'Flexbox', 309 | color: colours.purple, 310 | description: 'The Flexible Box Module, usually referred to as flexbox, was' + 311 | ' designed as a one-dimensional layout model, and as a method that could' + 312 | ' offer space distribution between items in an interface and powerful alignment capabilities.', 313 | fields: [ 314 | { 315 | name: 'MDN web docs', 316 | value: '[Flexbox documentation.](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)' 317 | }, 318 | { 319 | name: 'Flexbox quickstart guide', 320 | value: '[Learn flexbox in minutes! Easy, and powerful!](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)' 321 | }, 322 | { 323 | name: 'Flexbox Froggy', 324 | value: '[Get frogs to where they need to be with the magic of flexbox!](https://flexboxfroggy.com)' 325 | }, 326 | { 327 | name: 'Flexbox Defense', 328 | value: '[Blast enemies into nothingness with defensive towers postioned by flexbox!](http://www.flexboxdefense.com/)' 329 | } 330 | ] 331 | }, 332 | fetch: { 333 | title: 'JavaScript Fetch API', 334 | color: colours.purple, 335 | description: 'The Fetch API provides an interface for fetching resources' + 336 | ' (including across the network). It will seem familiar to anyone who has used' + 337 | ' XMLHttpRequest, but the new API provides a more powerful and flexible feature set.', 338 | fields: [ 339 | { 340 | name: 'MDN web docs', 341 | value: '[Fetch API documentation.](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)' 342 | }, 343 | { 344 | name: 'Fetch HTTPlib', 345 | value: '[A Fetch library that assists with learning how to use fetch.](https://codepen.io/papaamazon-the-flexboxer/project/editor/DWwjNM)' 346 | }, 347 | { 348 | name: 'Fetch tutorial', 349 | value: '[A tutorial that shows how to use fetch to get data from an API.](https://scotch.io/tutorials/how-to-use-the-javascript-fetch-api-to-get-data)' 350 | }, 351 | { 352 | name: 'MDN fetch useage example', 353 | value: '[A very in depth write up write-up on how to use fetch.](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch_)' 354 | } 355 | ] 356 | }, 357 | grid: { 358 | title: 'CSS Grid', 359 | color: colours.purple, 360 | description: 'CSS Grid Layout excels at dividing a page into major' + 361 | 'regions or defining the relationship in terms of size, position, and' + 362 | 'layer, between parts of a control built from HTML primitives. \n' + 363 | 'Like tables, grid layout enables an author to align elements into' + 364 | 'columns and rows. However, many more layouts are either possible or' + 365 | 'easier with CSS grid than they were with tables. For example, a grid' + 366 | 'container\'s child elements could position themselves so they actually' + 367 | 'overlap and layer, similar to CSS positioned elements.', 368 | fields: [ 369 | { 370 | name: 'MDN Web Docs', 371 | value: '[CSS Grid Layout Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout)' 372 | }, 373 | { 374 | name: 'CSS Grid Quickstart Guide', 375 | value: '[A quick and dirty guide to CSS Grid.](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout)' 376 | }, 377 | { 378 | name: 'CSS Grid Garden', 379 | value: '[A game for learning CSS Grid.](http://cssgridgarden.com/)' 380 | }, 381 | { 382 | name: 'CSS Grid Video Series', 383 | value: '[A CSS Grid video series by Wes Bos.](https://cssgrid.io/)' 384 | } 385 | ] 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | const migrate = require('./migrate') 2 | 3 | module.exports = { 4 | /** 5 | * Run DB schema migrations 6 | */ 7 | migrate 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/migrate.js: -------------------------------------------------------------------------------- 1 | import { dbFile, migrationDir } from '../config' 2 | import Database from 'better-sqlite3' 3 | import fs from 'fs' 4 | import { join } from 'path' 5 | 6 | /** 7 | * Run database schema migrations 8 | */ 9 | module.exports = () => { 10 | const db = new Database(dbFile) 11 | const dir = fs.readdirSync(migrationDir).sort() 12 | 13 | for (const migrationFile of dir) { 14 | db.exec(fs.readFileSync(join(migrationDir, migrationFile), 'utf8')) 15 | } 16 | } 17 | --------------------------------------------------------------------------------