├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
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 |
5 |
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 |
--------------------------------------------------------------------------------