├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── release.yml └── workflows │ └── CI.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── @types │ └── perspective-api-client.d.ts ├── Commands │ └── Moderation │ │ └── automod.ts ├── Events │ ├── Client │ │ ├── interactionCreate.ts │ │ └── ready.ts │ └── Moderation │ │ └── AI.engine.ts ├── Structures │ ├── Classes │ │ ├── Boxen.ts │ │ └── Client.ts │ ├── Design │ │ ├── color.ts │ │ ├── icons.ts │ │ └── index.ts │ ├── Handlers │ │ └── Handler.ts │ ├── Interfaces │ │ ├── Command.ts │ │ ├── Config.ts │ │ ├── Event.ts │ │ └── index.ts │ └── Schemas │ │ └── ModerationDB.ts ├── config.ts └── main.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Encoding 4 | charset = utf-8 5 | 6 | # Indentation: 7 | [{*.ts,.js}] 8 | tab_width = 4 9 | indent_style = tab 10 | 11 | # Style: 12 | [{*.ts,.js}] 13 | insert_final_newline = true 14 | trim_trailing_whitespace = true 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # NPM Packages 2 | node_modules 3 | 4 | # Distribution 5 | **/dist/** 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "prettier" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint", 17 | "prettier" 18 | ], 19 | "rules": { 20 | // Config 21 | "no-undef": "off", 22 | "camelcase": "error", 23 | "arrow-spacing": "error", 24 | "no-unused-vars": "warn", 25 | "no-inner-declarations": "off", 26 | "no-var": "error", 27 | "no-unexpected-multiline": "error", 28 | // Prettier 29 | "prettier/prettier": [ 30 | "error", 31 | {}, 32 | { 33 | "usePrettierrc": true, 34 | "endOfLine": "auto" 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | categories: 6 | - title: Core Changes 7 | labels: 8 | - MAJOR 9 | - core 10 | - next 11 | - breaking 12 | - title: Enhancements 13 | labels: 14 | - MINOR 15 | - enhancement 16 | - title: Bug Fixes 17 | labels: 18 | - PATCH 19 | - bug 20 | - bugfix 21 | - fix 22 | - bugfixes 23 | - fixes 24 | - title: Documentation Changes 25 | labels: 26 | - docs 27 | - documentation-release 28 | - title: Example Changes 29 | labels: 30 | - examples 31 | - examples-release 32 | - title: Misc Changes 33 | labels: 34 | - "*" -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | tags: [v*] 7 | jobs: 8 | build: 9 | name: Build 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 16 21 | 22 | - name: Install 23 | run: npm ci 24 | 25 | - name: Build 26 | run: npm run build 27 | 28 | lint: 29 | name: Lint 30 | 31 | runs-on: ubuntu-latest 32 | 33 | needs: build 34 | 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | 39 | - name: Setup Node.js 40 | uses: actions/setup-node@v3 41 | with: 42 | node-version: 16 43 | 44 | - name: Install 45 | run: npm ci 46 | 47 | - name: Build 48 | run: npm run build 49 | 50 | - name: Lint 51 | run: npm run lint -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Packages 2 | node_modules/ 3 | 4 | # Log files 5 | logs/ 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Env 15 | .env 16 | 17 | # Dist 18 | dist/ 19 | 20 | # Miscellaneous 21 | .tmp/ 22 | .vscode/* 23 | !.vscode/extensions.json 24 | !.vscode/settings.json 25 | .idea/ 26 | .DS_Store 27 | .turbo 28 | tsconfig.tsbuildinfo 29 | coverage/ 30 | 31 | # yarn 32 | .pnp.* 33 | .yarn/* 34 | !.yarn/patches 35 | !.yarn/plugins 36 | !.yarn/releases 37 | !.yarn/sdks 38 | !.yarn/versions 39 | 40 | # Cache 41 | .prettiercache 42 | .eslintcache -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # NPM Packages 2 | node_modules 3 | 4 | # Distribution 5 | **/dist/** 6 | 7 | # ESLint 8 | .eslintrc.json 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "useTabs": false, 4 | 5 | "singleQuote": true, 6 | "editorconfig": true, 7 | 8 | "printWidth": 100, 9 | "semi": true, 10 | "trailingComma": "all", 11 | 12 | "arrowParens": "always", 13 | "bracketSpacing": true, 14 | "parser": "typescript", 15 | "endOfLine": "auto" 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Amit Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Moderation 2 | 3 | An advanced AI moderation system for Discord with [Discord.js](https://discord.js.org). 4 | 5 | ![AI Moderation](https://user-images.githubusercontent.com/94821119/183252082-2accef35-876b-4223-990b-9dd776483fef.png) 6 | 7 | ## Install 8 | 9 | ``` 10 | npm install 11 | ``` 12 | 13 | ## NOTE! 14 | 15 | The [chartjs-node-canvas](https://github.com/SeanSobey/ChartjsNodeCanvas#node-js-version) does not yet support Node.js v18.x. You can go through the issue [here](https://github.com/SeanSobey/ChartjsNodeCanvas/issues/107#issuecomment-1140344190). 16 | 17 | **Solution**: Use Node.js v16.x. 18 | 19 | ## License 20 | 21 | AI-Moderation is available under the [MIT License](./LICENSE) 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "boxen": "^7.0.0", 4 | "chalk": "^5.0.1", 5 | "chart.js": "^3.9.1", 6 | "chartjs-node-canvas": "^4.1.6", 7 | "discord.js": "^14.1.2", 8 | "mongoose": "^6.5.1", 9 | "ms": "^2.1.3", 10 | "perspective-api-client": "^3.1.0", 11 | "wordle.engine": "^1.0.0" 12 | }, 13 | "name": "ai-moderation", 14 | "description": "An advanced AI moderation system for Discord with [Discord.js](https://discord.js.org).", 15 | "version": "1.0.0", 16 | "main": "dist/main.js", 17 | "scripts": { 18 | "start": "node dist/main.js", 19 | "dev": "nodemon dist/main.js", 20 | "build": "tsc", 21 | "watch": "tsc -w", 22 | "lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix" 23 | }, 24 | "type": "module", 25 | "devDependencies": { 26 | "@types/glob": "^7.2.0", 27 | "@types/ms": "^0.7.31", 28 | "@types/node": "^18.6.4", 29 | "@typescript-eslint/eslint-plugin": "^5.33.0", 30 | "@typescript-eslint/parser": "^5.33.0", 31 | "eslint": "^8.21.0", 32 | "eslint-config-node": "^4.1.0", 33 | "eslint-config-prettier": "^8.5.0", 34 | "eslint-plugin-import": "^2.26.0", 35 | "eslint-plugin-node": "^11.1.0", 36 | "eslint-plugin-prettier": "^4.2.1", 37 | "prettier": "^2.7.1", 38 | "prettier-eslint": "^15.0.1", 39 | "typescript": "^4.7.4" 40 | }, 41 | "keywords": [], 42 | "author": "", 43 | "license": "ISC" 44 | } 45 | -------------------------------------------------------------------------------- /src/@types/perspective-api-client.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'perspective-api-client'; 2 | -------------------------------------------------------------------------------- /src/Commands/Moderation/automod.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChannelType, 3 | ChatInputCommandInteraction, 4 | EmbedBuilder, 5 | SlashCommandBuilder, 6 | PermissionFlagsBits, 7 | } from 'discord.js'; 8 | import { BaseClient } from '../../Structures/Classes/Client.js'; 9 | import DB from '../../Structures/Schemas/ModerationDB.js'; 10 | import { icon } from '../../Structures/Design/index.js'; 11 | import { Command } from '../../Structures/Interfaces'; 12 | 13 | const command: Command = { 14 | data: new SlashCommandBuilder() 15 | .setName('automod') 16 | .setDescription('AI Based Moderation System') 17 | .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) 18 | .setDMPermission(false) 19 | .addSubcommandGroup((group) => 20 | group 21 | .setName('channel') 22 | .setDescription('Channels To Use Automod') 23 | .addSubcommand((subcommand) => 24 | subcommand 25 | .setName('add') 26 | .setDescription('Add Channel For Automod') 27 | .addChannelOption((option) => 28 | option 29 | .setName('channel') 30 | .setDescription('The Channel To Add') 31 | .addChannelTypes(ChannelType.GuildText) 32 | .setRequired(true), 33 | ), 34 | ) 35 | .addSubcommand((subcommand) => 36 | subcommand 37 | .setName('remove') 38 | .setDescription('Remove Channel For Automod') 39 | .addChannelOption((option) => 40 | option 41 | .setName('channel') 42 | .setDescription('The Channel To Remove') 43 | .addChannelTypes(ChannelType.GuildText) 44 | .setRequired(true), 45 | ), 46 | ), 47 | ) 48 | .addSubcommandGroup((group) => 49 | group 50 | .setName('bypass') 51 | .setDescription('Channels To Use Automod') 52 | .addSubcommand((subcommand) => 53 | subcommand 54 | .setName('add') 55 | .setDescription('Add Users/Roles To Automod Bypass') 56 | .addUserOption((option) => 57 | option 58 | .setName('user') 59 | .setDescription('Add User To Automod Bypass') 60 | .setRequired(false), 61 | ) 62 | .addRoleOption((option) => 63 | option 64 | .setName('role') 65 | .setDescription('Add Role To Automod Bypass') 66 | .setRequired(false), 67 | ), 68 | ) 69 | .addSubcommand((subcommand) => 70 | subcommand 71 | .setName('remove') 72 | .setDescription('Remove Users/Roles From Automod Bypass') 73 | .addUserOption((option) => 74 | option 75 | .setName('user') 76 | .setDescription('Remove User From Automod Bypass') 77 | .setRequired(false), 78 | ) 79 | .addRoleOption((option) => 80 | option 81 | .setName('role') 82 | .setDescription('Remove Role From Automod Bypass') 83 | .setRequired(false), 84 | ), 85 | ) 86 | .addSubcommand((subcommand) => 87 | subcommand.setName('list').setDescription('List Automod Bypass'), 88 | ), 89 | ) 90 | .addSubcommandGroup((group) => 91 | group 92 | .setName('log') 93 | .setDescription('Configure Automod Logging Channels') 94 | .addSubcommand((subcommand) => 95 | subcommand 96 | .setName('add') 97 | .setDescription('Add Channel To Automod Logging') 98 | .addChannelOption((option) => 99 | option 100 | .setName('channel') 101 | .setDescription('The Channel For Automod Logging') 102 | .addChannelTypes(ChannelType.GuildText, ChannelType.GuildNews) 103 | .setRequired(false), 104 | ), 105 | ) 106 | .addSubcommand((subcommand) => 107 | subcommand 108 | .setName('remove') 109 | .setDescription('Remove Channel From Automod Logging') 110 | .addChannelOption((option) => 111 | option 112 | .setName('channel') 113 | .setDescription('The Channel For Automod Logging') 114 | .addChannelTypes(ChannelType.GuildText, ChannelType.GuildNews) 115 | .setRequired(false), 116 | ), 117 | ), 118 | ) 119 | .addSubcommandGroup((group) => 120 | group 121 | .setName('config') 122 | .setDescription('Configure Automod') 123 | .addSubcommand((subcommand) => 124 | subcommand.setName('list').setDescription('Show All Automod Configurations'), 125 | ), 126 | ) 127 | .addSubcommand((subcommand) => 128 | subcommand 129 | .setName('punishments') 130 | .setDescription('Configure Automod Punishments') 131 | .addStringOption((option) => 132 | option 133 | .setName('low') 134 | .setDescription('Set Low Severity Punishment') 135 | .setRequired(true) 136 | .addChoices( 137 | { name: 'Delete', value: 'delete' }, 138 | { name: 'Timeout', value: 'timeout' }, 139 | { name: 'Kick', value: 'kick' }, 140 | { name: 'Ban', value: 'ban' }, 141 | ), 142 | ) 143 | .addStringOption((option) => 144 | option 145 | .setName('medium') 146 | .setDescription('Set Medium Severity Punishment') 147 | .setRequired(true) 148 | .addChoices( 149 | { name: 'Delete', value: 'delete' }, 150 | { name: 'Timeout', value: 'timeout' }, 151 | { name: 'Kick', value: 'kick' }, 152 | { name: 'Ban', value: 'ban' }, 153 | ), 154 | ) 155 | .addStringOption((option) => 156 | option 157 | .setName('high') 158 | .setDescription('Set High Severity Punishment') 159 | .setRequired(true) 160 | .addChoices( 161 | { name: 'Delete', value: 'delete' }, 162 | { name: 'Timeout', value: 'timeout' }, 163 | { name: 'Kick', value: 'kick' }, 164 | { name: 'Ban', value: 'ban' }, 165 | ), 166 | ), 167 | ), 168 | async execute(interaction: ChatInputCommandInteraction, client: BaseClient) { 169 | const { options, guild } = interaction; 170 | const docs = await DB.findOne({ GuildID: guild?.id }); 171 | 172 | const NotConfiguredEmbed = new EmbedBuilder() 173 | .setColor('Red') 174 | .setTitle('🛑 Automod Not Configured!') 175 | .setDescription( 176 | `Please configure automod first.\nUse \`/automod channel add\`, \`/automod log\` & \`/automod punishments\` to configure automod`, 177 | ); 178 | 179 | switch (options.getSubcommand()) { 180 | case 'add': 181 | { 182 | switch (options.getSubcommandGroup()) { 183 | case 'channel': 184 | { 185 | const channel = options.getChannel('channel'); 186 | 187 | if (!docs) { 188 | await DB.create({ 189 | GuildID: guild?.id, 190 | ChannelIDs: channel?.id, 191 | }); 192 | 193 | await interaction.reply({ 194 | embeds: [ 195 | new EmbedBuilder() 196 | .setColor('Green') 197 | .setDescription( 198 | `${channel} Has Been Added to the Automod Channels`, 199 | ), 200 | ], 201 | ephemeral: true, 202 | }); 203 | } else if (docs && docs.ChannelIDs.includes(channel?.id)) { 204 | return interaction.reply({ 205 | embeds: [ 206 | new EmbedBuilder() 207 | .setColor('Red') 208 | .setDescription( 209 | `${channel} Is Already In The Automod Channels`, 210 | ), 211 | ], 212 | ephemeral: true, 213 | }); 214 | } 215 | 216 | docs?.ChannelIDs.push(channel?.id); 217 | await docs?.save(); 218 | 219 | await interaction.reply({ 220 | embeds: [ 221 | new EmbedBuilder() 222 | .setColor('Green') 223 | .setDescription( 224 | `${channel} Has Been Added to the Automod Channels`, 225 | ), 226 | ], 227 | ephemeral: true, 228 | }); 229 | } 230 | break; 231 | 232 | case 'bypass': 233 | { 234 | const addUser = options.getUser('user'); 235 | const addRole = options.getRole('role'); 236 | 237 | if (addUser) { 238 | try { 239 | if (!docs) { 240 | await DB.create({ 241 | GuildID: guild?.id, 242 | BypassUsers: addUser.id, 243 | }); 244 | } else if (docs && docs.BypassUsers.includes(addUser.id)) { 245 | return interaction.reply({ 246 | embeds: [ 247 | new EmbedBuilder() 248 | .setColor('Red') 249 | .setDescription( 250 | `${addUser} Is Already In The Automod Bypass List`, 251 | ), 252 | ], 253 | ephemeral: true, 254 | }); 255 | } else { 256 | docs.BypassUsers.push(addUser.id); 257 | await docs.save(); 258 | } 259 | 260 | await interaction.reply({ 261 | embeds: [ 262 | new EmbedBuilder() 263 | .setColor('Green') 264 | .setDescription( 265 | `${addUser} Has Been Added to the Automod Bypass List`, 266 | ), 267 | ], 268 | ephemeral: true, 269 | }); 270 | } catch (err) { 271 | console.log(err); 272 | } 273 | } else if (addRole) { 274 | try { 275 | if (!docs) { 276 | await DB.create({ 277 | GuildID: guild?.id, 278 | BypassRoles: addRole.id, 279 | }); 280 | } else if (docs && docs.BypassRoles.includes(addRole.id)) { 281 | return interaction.reply({ 282 | embeds: [ 283 | new EmbedBuilder() 284 | .setColor('Red') 285 | .setDescription( 286 | `${addRole} Is Already In The Automod Bypass List`, 287 | ), 288 | ], 289 | ephemeral: true, 290 | }); 291 | } 292 | 293 | docs?.BypassRoles.push(addRole.id); 294 | await docs?.save(); 295 | 296 | await interaction.reply({ 297 | embeds: [ 298 | new EmbedBuilder() 299 | .setColor('Green') 300 | .setDescription( 301 | `${addRole} Has Been Added to the Automod Bypass List`, 302 | ), 303 | ], 304 | ephemeral: true, 305 | }); 306 | } catch (err) { 307 | console.log(err); 308 | } 309 | } else { 310 | return interaction.reply({ 311 | embeds: [ 312 | new EmbedBuilder() 313 | .setColor('Red') 314 | .setTitle('🛑 Invalid Arguments') 315 | .setDescription( 316 | `Please Provide A User/Role To Add To The Automod Bypass List`, 317 | ), 318 | ], 319 | ephemeral: true, 320 | }); 321 | } 322 | } 323 | break; 324 | 325 | case 'log': 326 | { 327 | try { 328 | const addChannel = options.getChannel('channel'); 329 | const docs = await DB.findOne({ GuildID: guild?.id }); 330 | 331 | if (!docs) { 332 | await DB.create({ 333 | GuildID: guild?.id, 334 | LogChannelIDs: addChannel?.id, 335 | }); 336 | } else if ( 337 | docs && 338 | docs.LogChannelIDs.includes(addChannel?.id) 339 | ) { 340 | return interaction.reply({ 341 | embeds: [ 342 | new EmbedBuilder() 343 | .setColor('Red') 344 | .setDescription( 345 | `${addChannel} Is Already In The Automod Logging Channels`, 346 | ), 347 | ], 348 | ephemeral: true, 349 | }); 350 | } 351 | 352 | docs?.LogChannelIDs.push(addChannel?.id); 353 | await docs?.save(); 354 | 355 | await interaction.reply({ 356 | embeds: [ 357 | new EmbedBuilder() 358 | .setColor('Green') 359 | .setDescription( 360 | `${addChannel} Has Been Added to the Automod Logging Channels`, 361 | ), 362 | ], 363 | ephemeral: true, 364 | }); 365 | } catch (err) { 366 | console.log(err); 367 | } 368 | } 369 | break; 370 | } 371 | } 372 | break; 373 | 374 | case 'remove': 375 | { 376 | switch (options.getSubcommandGroup()) { 377 | case 'channel': 378 | { 379 | try { 380 | const channel = options.getChannel('channel'); 381 | 382 | if (!docs || docs.ChannelIDs.length < 1) { 383 | return interaction.reply({ 384 | embeds: [NotConfiguredEmbed], 385 | ephemeral: true, 386 | }); 387 | } else if (!docs.ChannelIDs.includes(channel?.id)) { 388 | return interaction.reply({ 389 | embeds: [ 390 | new EmbedBuilder() 391 | .setColor('Red') 392 | .setDescription( 393 | `${channel} is not in the automod channels`, 394 | ), 395 | ], 396 | ephemeral: true, 397 | }); 398 | } 399 | 400 | docs.ChannelIDs.splice(docs.ChannelIDs.indexOf(channel?.id), 1); 401 | await docs.save(); 402 | 403 | await interaction.reply({ 404 | embeds: [ 405 | new EmbedBuilder() 406 | .setColor('Green') 407 | .setDescription( 408 | `${channel} has been removed from the automod channels`, 409 | ), 410 | ], 411 | ephemeral: true, 412 | }); 413 | } catch (err) { 414 | console.log(err); 415 | } 416 | } 417 | break; 418 | 419 | case 'bypass': 420 | { 421 | try { 422 | const removeUser = options.getUser('user'); 423 | const removeRole = options.getRole('role'); 424 | 425 | if (removeUser) { 426 | if (!docs || docs.BypassUsers.length < 1) { 427 | return interaction.reply({ 428 | embeds: [NotConfiguredEmbed], 429 | ephemeral: true, 430 | }); 431 | } else if (!docs.BypassUsers.includes(removeUser?.id)) { 432 | return interaction.reply({ 433 | embeds: [ 434 | new EmbedBuilder() 435 | .setColor('Red') 436 | .setDescription( 437 | `${removeUser} is not in the Automod Bypass List`, 438 | ), 439 | ], 440 | ephemeral: true, 441 | }); 442 | } 443 | 444 | docs.BypassUsers = removeOne( 445 | docs.BypassUsers, 446 | removeUser?.id, 447 | ); 448 | await docs.save(); 449 | 450 | return interaction.reply({ 451 | embeds: [ 452 | new EmbedBuilder() 453 | .setColor('Green') 454 | .setDescription( 455 | `${removeUser} has been removed from the Automod Bypass List`, 456 | ), 457 | ], 458 | ephemeral: true, 459 | }); 460 | } else if (removeRole) { 461 | if (!docs || docs.BypassRoles.length < 1) { 462 | return interaction.reply({ 463 | embeds: [NotConfiguredEmbed], 464 | ephemeral: true, 465 | }); 466 | } else if (!docs.BypassRoles.includes(removeRole?.id)) { 467 | return interaction.reply({ 468 | embeds: [ 469 | new EmbedBuilder() 470 | .setColor('Red') 471 | .setDescription( 472 | `${removeRole} is not in the Automod Bypass List`, 473 | ), 474 | ], 475 | ephemeral: true, 476 | }); 477 | } 478 | 479 | docs.BypassRoles = removeOne( 480 | docs.BypassRoles, 481 | removeRole.id, 482 | ); 483 | await docs.save(); 484 | 485 | return interaction.reply({ 486 | embeds: [ 487 | new EmbedBuilder() 488 | .setColor('Green') 489 | .setDescription( 490 | `${removeRole} has been removed from the Automod Bypass List`, 491 | ), 492 | ], 493 | ephemeral: true, 494 | }); 495 | } 496 | } catch (err) { 497 | console.log(err); 498 | } 499 | } 500 | break; 501 | 502 | case 'log': 503 | { 504 | try { 505 | const removeChannel = options.getChannel('channel'); 506 | 507 | if (!docs || docs.LogChannelIDs.length < 1) { 508 | return interaction.reply({ 509 | embeds: [NotConfiguredEmbed], 510 | ephemeral: true, 511 | }); 512 | } else if (!docs.LogChannelIDs.includes(removeChannel?.id)) { 513 | return interaction.reply({ 514 | embeds: [ 515 | new EmbedBuilder() 516 | .setColor('Red') 517 | .setDescription( 518 | `${removeChannel} is not in the Automod Log Channel List`, 519 | ), 520 | ], 521 | ephemeral: true, 522 | }); 523 | } 524 | 525 | docs.LogChannelIDs = removeOne( 526 | docs.LogChannelIDs, 527 | removeChannel?.id, 528 | ); 529 | await docs.save(); 530 | 531 | return interaction.reply({ 532 | embeds: [ 533 | new EmbedBuilder() 534 | .setColor('Green') 535 | .setDescription( 536 | `${removeChannel} has been removed from the Automod Log Channel List`, 537 | ), 538 | ], 539 | ephemeral: true, 540 | }); 541 | } catch (err) { 542 | console.log(err); 543 | } 544 | } 545 | break; 546 | } 547 | } 548 | break; 549 | 550 | case 'list': 551 | { 552 | switch (options.getSubcommandGroup()) { 553 | case 'bypass': 554 | { 555 | try { 556 | if (!docs) { 557 | return interaction.reply({ 558 | embeds: [NotConfiguredEmbed], 559 | ephemeral: true, 560 | }); 561 | } else { 562 | return interaction.reply({ 563 | embeds: [ 564 | new EmbedBuilder() 565 | .setColor('Green') 566 | .setTitle('Automod Bypass List') 567 | .addFields( 568 | { 569 | name: `${icon.reply.default} Users (${docs.BypassUsers.length})`, 570 | value: `${ 571 | docs.BypassUsers.map((user) => { 572 | return `<@${user}>`; 573 | }).join(', ') || `None` 574 | }\nㅤ`, 575 | inline: false, 576 | }, 577 | { 578 | name: `${icon.reply.default} Roles (${docs.BypassRoles.length})`, 579 | value: `${ 580 | docs.BypassRoles.map((role) => { 581 | return `<@&${role}>`; 582 | }).join(', ') || `None` 583 | }\nㅤ`, 584 | inline: false, 585 | }, 586 | ), 587 | ], 588 | ephemeral: true, 589 | }); 590 | } 591 | } catch (err) { 592 | console.log(err); 593 | } 594 | } 595 | break; 596 | 597 | case 'config': 598 | { 599 | try { 600 | const ChannelIDs: any[] = []; 601 | const Punishment: any[] = []; 602 | const LogChannelIDs: any[] = []; 603 | const Severity = ['Low', 'Medium', 'High']; 604 | 605 | if (!docs || docs.ChannelIDs.length < 1) { 606 | return interaction.reply({ 607 | embeds: [NotConfiguredEmbed], 608 | ephemeral: true, 609 | }); 610 | } 611 | 612 | await FetchAndPush(); 613 | 614 | async function FetchAndPush() { 615 | docs?.ChannelIDs.forEach(async (c) => { 616 | const channel = await client.channels.fetch(c); 617 | ChannelIDs.push(channel!); 618 | }); 619 | 620 | docs?.LogChannelIDs.forEach(async (c) => { 621 | const channel = await client.channels.fetch(c); 622 | LogChannelIDs.push(channel!); 623 | }); 624 | 625 | docs?.Punishments.map(async (action: string, i: number) => 626 | Punishment.push( 627 | `**${Severity[i]}**: ${ 628 | action.toUpperCase() || 'None' 629 | }`, 630 | ), 631 | ); 632 | } 633 | 634 | return interaction.reply({ 635 | embeds: [ 636 | new EmbedBuilder() 637 | .setColor('Green') 638 | .setTitle(`Automod Configuration`) 639 | .addFields( 640 | { 641 | name: `${icon.reply.default} Channels (${ChannelIDs.length})`, 642 | value: `${ 643 | ChannelIDs.join('\n') || 'None' 644 | }\nㅤ`, 645 | inline: false, 646 | }, 647 | { 648 | name: `${icon.reply.default} Punishments`, 649 | value: `${Punishment.join('\n')}\nㅤ`, 650 | inline: false, 651 | }, 652 | { 653 | name: `${icon.reply.default} Logging (${LogChannelIDs.length})`, 654 | value: `${ 655 | LogChannelIDs.join('\n') || 'None' 656 | }\nㅤ`, 657 | inline: false, 658 | }, 659 | { 660 | name: `${icon.reply.default} Bypass`, 661 | value: `\`•\` **Users:** ${ 662 | docs.BypassUsers.length || 'None' 663 | } \n\`•\` **Roles:** ${ 664 | docs.BypassRoles.length || 'None' 665 | }\nㅤ`, 666 | inline: false, 667 | }, 668 | ), 669 | ], 670 | ephemeral: true, 671 | }); 672 | } catch (err) { 673 | console.log(err); 674 | } 675 | } 676 | break; 677 | } 678 | } 679 | break; 680 | 681 | case 'punishments': 682 | { 683 | const low = options.getString('low'); 684 | const medium = options.getString('medium'); 685 | const high = options.getString('high'); 686 | 687 | const Punishment: any[] = []; 688 | const Severity = ['Low', 'Medium', 'High']; 689 | 690 | const docs = await DB.findOneAndUpdate( 691 | { GuildID: guild?.id }, 692 | { 693 | Punishments: [low, medium, high], 694 | }, 695 | { new: true, upsert: true }, 696 | ); 697 | 698 | docs?.Punishments.map(async (action: string, i: number) => 699 | Punishment.push(`**${Severity[i]}**: ${action.toUpperCase() || 'None'}`), 700 | ); 701 | 702 | await interaction.reply({ 703 | embeds: [ 704 | new EmbedBuilder() 705 | .setColor('Green') 706 | .setTitle(`Automod Punishments`) 707 | .setDescription(`${Punishment.join('\n')}\nㅤ`), 708 | ], 709 | ephemeral: true, 710 | }); 711 | } 712 | break; 713 | } 714 | }, 715 | }; 716 | 717 | /**Remove Single Value From Array */ 718 | function removeOne(arr: any[], value: any) { 719 | const index = arr.indexOf(value); 720 | if (index > -1) { 721 | arr.splice(index, 1); 722 | } 723 | return arr; 724 | } 725 | 726 | export default command; 727 | -------------------------------------------------------------------------------- /src/Events/Client/interactionCreate.ts: -------------------------------------------------------------------------------- 1 | import { CommandInteraction, EmbedBuilder } from 'discord.js'; 2 | import { BaseClient } from '../../Structures/Classes/Client.js'; 3 | import { Event } from '../../Structures/Interfaces/Event.js'; 4 | 5 | const event: Event = { 6 | name: 'interactionCreate', 7 | options: { 8 | ONCE: false, 9 | REST: false, 10 | }, 11 | 12 | execute(interaction: CommandInteraction, client: BaseClient) { 13 | if (!interaction.isChatInputCommand()) return; 14 | 15 | const command = client.commands.get(interaction.commandName); 16 | 17 | if (!command) { 18 | return interaction.reply({ 19 | embeds: [ 20 | new EmbedBuilder() 21 | .setColor('Red') 22 | .setTitle('🛑 Error') 23 | .setDescription('This command is outdated.'), 24 | ], 25 | ephemeral: true, 26 | }); 27 | } 28 | 29 | command.execute(interaction, client); 30 | }, 31 | }; 32 | 33 | export default event; 34 | -------------------------------------------------------------------------------- /src/Events/Client/ready.ts: -------------------------------------------------------------------------------- 1 | import { ActivityType, Client } from 'discord.js'; 2 | import { Event } from '../../Structures/Interfaces/Event.js'; 3 | 4 | const { Watching } = ActivityType; 5 | 6 | const event: Event = { 7 | name: 'ready', 8 | options: { 9 | ONCE: true, 10 | }, 11 | 12 | execute: async (client: Client) => { 13 | client.user?.setPresence({ 14 | activities: [ 15 | { 16 | name: `github.com/AmitKumarHQ`, 17 | type: Watching, 18 | }, 19 | ], 20 | status: 'online', 21 | }); 22 | }, 23 | }; 24 | 25 | export default event; 26 | -------------------------------------------------------------------------------- /src/Events/Moderation/AI.engine.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AttachmentBuilder, 3 | ChannelType, 4 | EmbedBuilder, 5 | Message, 6 | TextChannel, 7 | MessageOptions, 8 | } from 'discord.js'; 9 | import { BaseClient } from '../../Structures/Classes/Client.js'; 10 | import { Event } from '../../Structures/Interfaces/Event.js'; 11 | import { color } from '../../Structures/Design/color.js'; 12 | import { ChartJSNodeCanvas } from 'chartjs-node-canvas'; 13 | import { icon } from '../../Structures/Design/icons.js'; 14 | import Perspective from 'perspective-api-client'; 15 | import { ChartConfiguration } from 'chart.js'; 16 | import ms from 'ms'; 17 | 18 | import DB from '../../Structures/Schemas/ModerationDB.js'; 19 | 20 | interface IScore { 21 | name: string; 22 | value: number; 23 | } 24 | 25 | const event: Event = { 26 | name: 'messageCreate', 27 | execute: async (message: Message, client: BaseClient) => { 28 | if (message.channel.type === ChannelType.DM) return; 29 | 30 | const msg = await message.fetch(); 31 | const { author, member, guild, channel } = msg; 32 | 33 | const perspective = new Perspective({ apiKey: client.config.APIs![0].apiKey }); 34 | 35 | // DATABASE 36 | const docs = await DB.findOne({ GuildID: msg.guildId }); 37 | if (!docs) return; 38 | 39 | const { Punishments, LogChannelIDs, ChannelIDs, BypassUsers, BypassRoles } = docs; 40 | const [PLow, PMed, PHigh] = Punishments; 41 | const LogChannels = LogChannelIDs; 42 | 43 | // BYPASS CHECKS 44 | try { 45 | const isMemberAdmin = (await guild?.members.fetch({ user: author }))?.permissions.has( 46 | 'Administrator', 47 | true, 48 | ); 49 | 50 | if (author.id === client.user?.id || author.bot) return; 51 | if (BypassUsers.includes(author.id) || isMemberAdmin) return; 52 | if (BypassRoles.length) { 53 | for (const role of BypassRoles) { 54 | if (member?.roles.cache.has(role)) return; 55 | } 56 | } 57 | } catch (err) { 58 | console.log(err); 59 | } 60 | 61 | // AI ANALYSIS 62 | const SCORES: IScore[] = await analyzeMessage(msg.content); 63 | const AvgScore = SCORES.reduce((a, b) => a + b.value, 0) / SCORES.length; 64 | 65 | const findAttribute = (name: string, arr: IScore[]) => 66 | arr.find((attr) => attr.name === name); 67 | 68 | // CHART GENERATION 69 | const INSULT_SCORE = await findAttribute('Insult', SCORES); 70 | const TOXICITY_SCORE = await findAttribute('Toxicity', SCORES); 71 | const PROFANITY_SCORE = await findAttribute('Profanity', SCORES); 72 | 73 | /** 74 | * Create a new Canvas for the chart 75 | */ 76 | const Canvas = new ChartJSNodeCanvas({ 77 | width: 750, 78 | height: 360, 79 | chartCallback: (ChartJS) => {}, 80 | }); 81 | 82 | const ChartConfig: ChartConfiguration = { 83 | type: 'bar', 84 | data: { 85 | labels: ['Insult', 'Toxicity', 'Profanity'], 86 | datasets: [ 87 | { 88 | label: 'Score', 89 | data: [INSULT_SCORE!.value, TOXICITY_SCORE!.value, PROFANITY_SCORE!.value], 90 | borderRadius: 20, 91 | borderWidth: 8, 92 | maxBarThickness: 80, 93 | borderColor: [ 94 | `${color.Material.RED}`, 95 | `${color.Material.YELLOW}`, 96 | `${color.Material.BLUE}`, 97 | ], 98 | backgroundColor: [ 99 | `${color.Material.RED}50`, 100 | `${color.Material.YELLOW}50`, 101 | `${color.Material.BLUE}50`, 102 | ], 103 | }, 104 | ], 105 | }, 106 | plugins: [], 107 | options: { 108 | responsive: false, 109 | indexAxis: 'y', 110 | scales: { 111 | y: { 112 | beginAtZero: true, 113 | }, 114 | }, 115 | plugins: { 116 | title: { 117 | display: false, 118 | text: 'Scores', 119 | }, 120 | legend: { 121 | display: false, 122 | position: 'right', 123 | }, 124 | }, 125 | }, 126 | }; 127 | 128 | const image = await Canvas.renderToBuffer(ChartConfig); 129 | const attachment = new AttachmentBuilder(image, { 130 | name: 'chart.png', 131 | description: 'AI Analysis Scores', 132 | }); 133 | 134 | // ACTION 135 | if (!ChannelIDs.includes(channel.id)) return; 136 | await takeAction(SCORES); 137 | 138 | async function takeAction(SCORES: IScore[]) { 139 | const Embeds = { 140 | Delete: new EmbedBuilder() 141 | .setColor('Red') 142 | .setTitle(`[MOD] Deleted a message`) 143 | .setDescription( 144 | ` 145 | ${icon.reply.continue.start} **User**: ${author} (${author.id}) 146 | ${icon.reply.continue.end} **Channel**: ${channel} 147 | `, 148 | ) 149 | .addFields( 150 | { name: 'Message', value: `${message}`, inline: false }, 151 | { 152 | name: 'Scores', 153 | value: SCORES.map( 154 | (score) => 155 | `${icon.reply.default} **${score.name}**: ${score.value}`, 156 | ).join('\n'), 157 | inline: false, 158 | }, 159 | ) 160 | .setImage('attachment://chart.png') 161 | .setTimestamp(), 162 | 163 | Timeout: new EmbedBuilder() 164 | .setColor('Red') 165 | .setTitle(`[MOD] Timed out a user`) 166 | .setDescription( 167 | ` 168 | ${icon.reply.continue.start} **User**: ${author} (${author.id}) 169 | ${icon.reply.continue.end} **Channel**: ${channel} 170 | `, 171 | ) 172 | .addFields( 173 | { name: 'Message', value: `${message}`, inline: false }, 174 | { 175 | name: 'Scores', 176 | value: SCORES.map( 177 | (score) => 178 | `${icon.reply.default} **${score.name}**: ${score.value}`, 179 | ).join('\n'), 180 | inline: false, 181 | }, 182 | ) 183 | .setImage('attachment://chart.png') 184 | .setTimestamp(), 185 | 186 | Kick: new EmbedBuilder() 187 | .setColor('Red') 188 | .setTitle(`[MOD] Kicked a user from the server`) 189 | .setDescription( 190 | ` 191 | ${icon.reply.continue.start} **User**: ${author} (${author.id}) 192 | ${icon.reply.continue.end} **Channel**: ${channel} 193 | `, 194 | ) 195 | .addFields( 196 | { name: 'Message', value: `${message}`, inline: false }, 197 | { 198 | name: 'Scores', 199 | value: SCORES.map( 200 | (score) => 201 | `${icon.reply.default} **${score.name}**: ${score.value}`, 202 | ).join('\n'), 203 | inline: false, 204 | }, 205 | ) 206 | .setImage('attachment://chart.png') 207 | .setTimestamp(), 208 | 209 | Ban: new EmbedBuilder() 210 | .setColor('Red') 211 | .setTitle(`[MOD] Banned a user from the server`) 212 | .setDescription( 213 | ` 214 | ${icon.reply.continue.start} **User**: ${author} (${author.id}) 215 | ${icon.reply.continue.end} **Channel**: ${channel} 216 | `, 217 | ) 218 | .addFields( 219 | { name: 'Message', value: `${message}`, inline: false }, 220 | { 221 | name: 'Scores', 222 | value: SCORES.map( 223 | (score) => 224 | `${icon.reply.default} **${score.name}**: ${score.value}`, 225 | ).join('\n'), 226 | inline: false, 227 | }, 228 | ) 229 | .setImage('attachment://chart.png') 230 | .setTimestamp(), 231 | }; 232 | 233 | if (AvgScore > 0.75 && AvgScore <= 0.8) { 234 | switch (PLow) { 235 | case 'delete': { 236 | await deleteMessageAction(msg, LogChannels, { 237 | embeds: [Embeds.Delete], 238 | files: [attachment], 239 | }); 240 | 241 | break; 242 | } 243 | 244 | case 'timeout': { 245 | timeoutAction( 246 | msg, 247 | LogChannels, 248 | { embeds: [Embeds.Timeout], files: [attachment] }, 249 | { 250 | embeds: [ 251 | Embeds.Timeout.setTitle( 252 | 'You Have Been Timed Out!', 253 | ).setDescription( 254 | `${author} You are on a 5 minutes timeout in **${guild?.name}** for Toxic Messages!`, 255 | ), 256 | ], 257 | files: [attachment], 258 | }, 259 | ); 260 | 261 | break; 262 | } 263 | 264 | case 'kick': { 265 | kickAction( 266 | msg, 267 | LogChannels, 268 | { embeds: [Embeds.Kick], files: [attachment] }, 269 | { 270 | embeds: [ 271 | Embeds.Kick.setTitle('You Have Been Kicked!').setDescription( 272 | `${author} You have been kicked from **${guild?.name}** for Toxic Messages!`, 273 | ), 274 | ], 275 | files: [attachment], 276 | }, 277 | ); 278 | 279 | break; 280 | } 281 | 282 | case 'ban': { 283 | banAction( 284 | msg, 285 | LogChannels, 286 | { embeds: [Embeds.Kick], files: [attachment] }, 287 | { 288 | embeds: [ 289 | Embeds.Ban.setTitle('You Have Been Banned!').setDescription( 290 | `${author} You have been banned from **${guild?.name}** for Toxic Messages!`, 291 | ), 292 | ], 293 | files: [attachment], 294 | }, 295 | ); 296 | 297 | break; 298 | } 299 | } 300 | } else if (AvgScore > 0.8 && AvgScore <= 0.85) { 301 | switch (PMed) { 302 | case 'delete': { 303 | await deleteMessageAction(msg, LogChannels, { 304 | embeds: [Embeds.Delete], 305 | files: [attachment], 306 | }); 307 | 308 | break; 309 | } 310 | 311 | case 'timeout': { 312 | timeoutAction( 313 | msg, 314 | LogChannels, 315 | { embeds: [Embeds.Timeout], files: [attachment] }, 316 | { 317 | embeds: [ 318 | Embeds.Timeout.setTitle( 319 | 'You Have Been Timed Out!', 320 | ).setDescription( 321 | `${author} You are on a 5 minutes timeout in **${guild?.name}** for Toxic Messages!`, 322 | ), 323 | ], 324 | files: [attachment], 325 | }, 326 | ); 327 | 328 | break; 329 | } 330 | 331 | case 'kick': { 332 | kickAction( 333 | msg, 334 | LogChannels, 335 | { embeds: [Embeds.Kick], files: [attachment] }, 336 | { 337 | embeds: [ 338 | Embeds.Kick.setTitle('You Have Been Kicked!').setDescription( 339 | `${author} You have been kicked from **${guild?.name}** for Toxic Messages!`, 340 | ), 341 | ], 342 | files: [attachment], 343 | }, 344 | ); 345 | 346 | break; 347 | } 348 | 349 | case 'ban': { 350 | banAction( 351 | msg, 352 | LogChannels, 353 | { embeds: [Embeds.Kick], files: [attachment] }, 354 | { 355 | embeds: [ 356 | Embeds.Ban.setTitle('You Have Been Banned!').setDescription( 357 | `${author} You have been banned from **${guild?.name}** for Toxic Messages!`, 358 | ), 359 | ], 360 | files: [attachment], 361 | }, 362 | ); 363 | 364 | break; 365 | } 366 | } 367 | } else if (AvgScore > 0.85 && AvgScore >= 0.9) { 368 | switch (PHigh) { 369 | case 'delete': { 370 | await deleteMessageAction(msg, LogChannels, { 371 | embeds: [Embeds.Delete], 372 | files: [attachment], 373 | }); 374 | 375 | break; 376 | } 377 | 378 | case 'timeout': { 379 | timeoutAction( 380 | msg, 381 | LogChannels, 382 | { embeds: [Embeds.Timeout], files: [attachment] }, 383 | { 384 | embeds: [ 385 | Embeds.Timeout.setTitle( 386 | 'You Have Been Timed Out!', 387 | ).setDescription( 388 | `${author} You are on a 5 minutes timeout in **${guild?.name}** for Toxic Messages!`, 389 | ), 390 | ], 391 | files: [attachment], 392 | }, 393 | ); 394 | 395 | break; 396 | } 397 | 398 | case 'kick': { 399 | kickAction( 400 | msg, 401 | LogChannels, 402 | { embeds: [Embeds.Kick], files: [attachment] }, 403 | { 404 | embeds: [ 405 | Embeds.Kick.setTitle('You Have Been Kicked!').setDescription( 406 | `${author} You have been kicked from **${guild?.name}** for Toxic Messages!`, 407 | ), 408 | ], 409 | files: [attachment], 410 | }, 411 | ); 412 | 413 | break; 414 | } 415 | 416 | case 'ban': { 417 | banAction( 418 | msg, 419 | LogChannels, 420 | { embeds: [Embeds.Kick], files: [attachment] }, 421 | { 422 | embeds: [ 423 | Embeds.Ban.setTitle('You Have Been Banned!').setDescription( 424 | `${author} You have been banned from **${guild?.name}** for Toxic Messages!`, 425 | ), 426 | ], 427 | files: [attachment], 428 | }, 429 | ); 430 | 431 | break; 432 | } 433 | } 434 | } 435 | } 436 | 437 | async function analyzeMessage(message: string) { 438 | const analyzeRequest = { 439 | comment: { 440 | text: message, 441 | }, 442 | requestedAttributes: { 443 | TOXICITY: {}, 444 | PROFANITY: {}, 445 | INSULT: {}, 446 | }, 447 | }; 448 | 449 | try { 450 | const speech = await perspective.analyze(analyzeRequest); 451 | 452 | const INSULT_SCORE: number = speech.attributeScores.INSULT.summaryScore.value; 453 | const TOXICITY_SCORE: number = speech.attributeScores.TOXICITY.summaryScore.value; 454 | const PROFANITY_SCORE: number = speech.attributeScores.PROFANITY.summaryScore.value; 455 | 456 | return new Promise((resolve: (value: any) => any, reject) => { 457 | resolve([ 458 | { name: 'Insult', value: INSULT_SCORE }, 459 | { name: 'Toxicity', value: TOXICITY_SCORE }, 460 | { name: 'Profanity', value: PROFANITY_SCORE }, 461 | ]); 462 | }); 463 | } catch (err) { 464 | console.log(err); 465 | } 466 | } 467 | }, 468 | }; 469 | 470 | async function deleteMessageAction(msg: Message, LogChannels: string[], SendData: MessageOptions) { 471 | await msg.delete(); 472 | 473 | LogChannels.forEach(async (id) => { 474 | const channel = msg.guild?.channels.cache.get(id) as TextChannel; 475 | await channel.send(SendData); 476 | }); 477 | 478 | return true; 479 | } 480 | 481 | async function timeoutAction( 482 | msg: Message, 483 | LogChannels: string[], 484 | SendData: MessageOptions, 485 | MemberSend: MessageOptions, 486 | ) { 487 | await msg.delete(); 488 | await msg.member?.timeout(ms('5m'), 'Toxicity Detected'); 489 | 490 | LogChannels.forEach(async (id) => { 491 | const channel = msg.guild?.channels.cache.get(id) as TextChannel; 492 | await channel.send(SendData); 493 | }); 494 | 495 | await msg.member?.send(MemberSend); 496 | } 497 | 498 | async function kickAction( 499 | msg: Message, 500 | LogChannels: string[], 501 | SendData: MessageOptions, 502 | MemberSend: MessageOptions, 503 | ) { 504 | await msg.delete(); 505 | await msg.member?.kick('Toxicity Detected'); 506 | 507 | LogChannels.forEach(async (id) => { 508 | const channel = msg.guild?.channels.cache.get(id) as TextChannel; 509 | await channel.send(SendData); 510 | }); 511 | 512 | await msg.member?.send(MemberSend); 513 | } 514 | 515 | async function banAction( 516 | msg: Message, 517 | LogChannels: string[], 518 | SendData: MessageOptions, 519 | MemberSend: MessageOptions, 520 | ) { 521 | await msg.delete(); 522 | await msg.member?.ban({ reason: 'Toxicity Detected' }); 523 | 524 | LogChannels.forEach(async (id) => { 525 | const channel = msg.guild?.channels.cache.get(id) as TextChannel; 526 | await channel.send(SendData); 527 | }); 528 | 529 | await msg.member?.send(MemberSend); 530 | } 531 | 532 | export default event; 533 | -------------------------------------------------------------------------------- /src/Structures/Classes/Boxen.ts: -------------------------------------------------------------------------------- 1 | import boxen, { Options } from 'boxen'; 2 | 3 | interface ItemOptions { 4 | name: string; 5 | value: string; 6 | } 7 | 8 | export class Box { 9 | constructor() {} 10 | 11 | public async createBox() { 12 | const arr: string[] = []; 13 | return arr; 14 | } 15 | 16 | public async addItem(box: string[], options: ItemOptions) { 17 | box.push(`${options.name}: ${options.value}`); 18 | } 19 | 20 | public async addItems(box: string[], options: ItemOptions[]) { 21 | options.forEach((item) => { 22 | box.push(`${item.name}: ${item.value}`); 23 | }); 24 | } 25 | 26 | public async showBox(box: string[], options: Options) { 27 | return console.log(boxen(box.join('\n'), options)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Structures/Classes/Client.ts: -------------------------------------------------------------------------------- 1 | import { Client, Collection, GatewayIntentBits, Partials, version } from 'discord.js'; 2 | import { Command, Config, Event } from '../Interfaces/index.js'; 3 | import { Handler } from '../Handlers/Handler.js'; 4 | import { Box } from '../Classes/Boxen.js'; 5 | 6 | import ClientConfig from '../../config.js'; 7 | import mongoose from 'mongoose'; 8 | import chalk from 'chalk'; 9 | 10 | const { Guilds, GuildMembers, GuildMessages } = GatewayIntentBits; 11 | const { User, Message, GuildMember, ThreadMember } = Partials; 12 | 13 | const IBox = new Box(); 14 | const BoxContents = await IBox.createBox(); 15 | 16 | export class BaseClient extends Client { 17 | public commands: Collection; 18 | public events: Collection; 19 | public config: Config; 20 | 21 | private boxContents = BoxContents; 22 | 23 | constructor() { 24 | super({ 25 | intents: [Guilds, GuildMembers, GuildMessages], 26 | partials: [User, Message, GuildMember, ThreadMember], 27 | }); 28 | 29 | this.commands = new Collection(); 30 | this.events = new Collection(); 31 | this.config = ClientConfig; 32 | } 33 | 34 | public async start() { 35 | // Login 36 | await this.login(this.config.TOKEN); 37 | await IBox.addItem(this.boxContents, { 38 | name: `${chalk.bold.hex('#5865F2')('Discord.js')}`, 39 | value: `v${version}\n`, 40 | }); 41 | 42 | // Modules 43 | await this.registerModules(); 44 | 45 | // Database 46 | await this.connectMongoDB(); 47 | 48 | await IBox.showBox(this.boxContents, { 49 | borderColor: 'white', 50 | borderStyle: 'round', 51 | dimBorder: true, 52 | padding: 1, 53 | margin: 1, 54 | }); 55 | } 56 | 57 | private async registerModules() { 58 | const { loadEvents, loadCommands } = new Handler(); 59 | 60 | try { 61 | await loadEvents(this); 62 | IBox.addItem(this.boxContents, { 63 | name: `${chalk.yellow('Events')}`, 64 | value: `OK`, 65 | }); 66 | } catch (err) { 67 | IBox.addItem(this.boxContents, { 68 | name: `${chalk.bold.red('Events')}`, 69 | value: `${err}`, 70 | }); 71 | } 72 | 73 | try { 74 | await loadCommands(this); 75 | IBox.addItem(this.boxContents, { 76 | name: `${chalk.yellow('Commands')}`, 77 | value: `OK`, 78 | }); 79 | } catch (err) { 80 | IBox.addItem(this.boxContents, { 81 | name: `${chalk.red('Commands')}`, 82 | value: `${err}`, 83 | }); 84 | } 85 | } 86 | 87 | private async connectMongoDB() { 88 | try { 89 | await mongoose.connect(`${this.config.Database?.MongoDB}`); 90 | IBox.addItem(this.boxContents, { 91 | name: `${chalk.yellow('Database')}`, 92 | value: `OK`, 93 | }); 94 | } catch (err) { 95 | IBox.addItem(this.boxContents, { 96 | name: `${chalk.red('Database')}`, 97 | value: `${err}`, 98 | }); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Structures/Design/color.ts: -------------------------------------------------------------------------------- 1 | export const color = { 2 | Transparent: '#00000000', 3 | All: { 4 | DEFAULT: '#000000', 5 | AQUA: '#1ABC9C', 6 | DARK_AQUA: '#11806A', 7 | GREEN: '#2ECC71', 8 | DARK_GREEN: '#1F8B4C', 9 | BLUE: '#3498DB', 10 | DARK_BLUE: '#206694', 11 | PURPLE: '#9B59B6', 12 | DARK_PURPLE: '#71368A', 13 | LUMINOUS_VIVID_PINK: '#E91E63', 14 | DARK_LUMINOUS_VIVID_PINK: '#AD1457', 15 | GOLD: '#F1C40F', 16 | DARK_GOLD: '#C27C0E', 17 | ORANGE: '#E67E22', 18 | DARK_ORANGE: '#A84300', 19 | RED: '#E74C3C', 20 | DARK_RED: '#992D22', 21 | GREY: '#95A5A6', 22 | DARK_GREY: '#979C9F', 23 | DARKER_GREY: '#7F8C8D', 24 | LIGHT_GREY: '#BCC0C0', 25 | NAVY: '#34495E', 26 | DARK_NAVY: '#2C3E50', 27 | YELLOW: '#FFFF00', 28 | }, 29 | Discord: { 30 | WHITE: '#FFFFFF', 31 | BLACK: '#000000', 32 | BURPLE: '#5865F2', 33 | FUSCHIA: '#EB459E', 34 | GREYPLE: '#99AAB5', 35 | DARK_BUT_NOT_BLACK: '#2C2F33', 36 | NOT_QUITE_BLACK: '#23272A', 37 | RED: '#ED4245', 38 | GREEN: '#57F287', 39 | YELLOW: '#FEE75C', 40 | }, 41 | Material: { 42 | RED: '#FF5555', 43 | BLUE: '#55AAFF', 44 | YELLOW: '#FFAA55', 45 | GREEN: '#55FF55', 46 | PINK: '#FF55FF', 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /src/Structures/Design/icons.ts: -------------------------------------------------------------------------------- 1 | export const icon = { 2 | color: { 3 | red: '<:icon_red:970322600771354634>', 4 | yellow: '<:icon_yellow:970322601887023125>', 5 | green: '<:icon_green:970322600930721802>', 6 | blue: '<:icon_blue:970322601878638712>', 7 | }, 8 | reply: { 9 | default: '<:icon_reply:962547429914337300>', 10 | continue: { 11 | start: '<:icon_ReplyContinue1:962547429813657611>', 12 | mid: '<:icon_ReplyContinue2:962547430061125632>', 13 | end: '<:icon_ReplyContinue3:962547429947867166>', 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/Structures/Design/index.ts: -------------------------------------------------------------------------------- 1 | export { color } from './color.js'; 2 | export { icon } from './icons.js'; 3 | -------------------------------------------------------------------------------- /src/Structures/Handlers/Handler.ts: -------------------------------------------------------------------------------- 1 | import { BaseClient } from '../Classes/Client.js'; 2 | import { Event, Command } from '../Interfaces/index.js'; 3 | import { pathToFileURL } from 'url'; 4 | import { promisify } from 'util'; 5 | import glob from 'glob'; 6 | import path from 'path'; 7 | 8 | const PG = promisify(glob); 9 | 10 | export class Handler { 11 | constructor() {} 12 | 13 | public async loadEvents(client: BaseClient) { 14 | const EventsDir = await PG(`${process.cwd()}/dist/Events/*/*{.ts,.js}`); 15 | 16 | EventsDir.forEach(async (file) => { 17 | const eventPath = path.resolve(file); 18 | const event: Event = (await import(`${pathToFileURL(eventPath)}`)).default; 19 | 20 | if (event.options?.ONCE) { 21 | client.once(event.name, (...args) => event.execute(...args, client)); 22 | } else { 23 | client.on(event.name, (...args) => event.execute(...args, client)); 24 | } 25 | 26 | client.events.set(event.name, event); 27 | }); 28 | } 29 | 30 | public async loadCommands(client: BaseClient) { 31 | let CmdArray: any[] = []; 32 | let DevArray: any[] = []; 33 | 34 | const CmdsDir = await PG(`${process.cwd()}/dist/Commands/*/*{.ts,.js}`); 35 | CmdsDir.forEach(async (file) => { 36 | const commandPath = path.resolve(file); 37 | const command: Command = (await import(`${pathToFileURL(commandPath)}`)).default; 38 | 39 | if (file.endsWith('.dev.ts') || file.endsWith('.dev.js')) { 40 | DevArray.push(command.data.toJSON()); 41 | client.commands.set(command.data.name, command); 42 | } else { 43 | CmdArray.push(command.data.toJSON()); 44 | client.commands.set(command.data.name, command); 45 | } 46 | 47 | // Register Commands 48 | client.on('ready', async () => { 49 | // PUBLIC Commands 50 | client.application?.commands.set(CmdArray); 51 | 52 | // DEV Commands 53 | client.config.DevGuilds.forEach(async (guild) => { 54 | await client.guilds.cache.get(guild.id)?.commands.set(DevArray); 55 | }); 56 | }); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Structures/Interfaces/Command.ts: -------------------------------------------------------------------------------- 1 | import { SlashCommandBuilder } from 'discord.js'; 2 | 3 | export interface Command { 4 | data: SlashCommandBuilder | any; 5 | execute: (...args: any[]) => any; 6 | } 7 | -------------------------------------------------------------------------------- /src/Structures/Interfaces/Config.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | TOKEN: string; 3 | Database: { 4 | MongoDB: string; 5 | Redis?: string; 6 | }; 7 | OwnerIds?: object[]; 8 | AdminIds?: object[]; 9 | Webhooks?: object[]; 10 | DevGuilds: [ 11 | { 12 | name: string; 13 | id: string; 14 | }, 15 | ]; 16 | APIs?: [ 17 | { 18 | name: string; 19 | apiKey: string; 20 | }, 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /src/Structures/Interfaces/Event.ts: -------------------------------------------------------------------------------- 1 | import { ClientEvents } from 'discord.js'; 2 | 3 | interface EventOptions { 4 | ONCE?: boolean; 5 | REST?: boolean; 6 | } 7 | 8 | export interface Event { 9 | name: keyof ClientEvents; 10 | options?: EventOptions; 11 | execute: (...args: any[]) => any; 12 | } 13 | -------------------------------------------------------------------------------- /src/Structures/Interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { Command } from './Command'; 2 | export { Config } from './Config'; 3 | export { Event } from './Event'; 4 | -------------------------------------------------------------------------------- /src/Structures/Schemas/ModerationDB.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | const { Schema, model } = mongoose; 3 | 4 | export default model( 5 | 'ModerationDB', 6 | new Schema({ 7 | GuildID: String, 8 | UserID: String, 9 | ChannelIDs: Array, 10 | WarnData: Array, 11 | KickData: Array, 12 | BanData: Array, 13 | 14 | // AI Moderation System 15 | Punishments: Array, 16 | LogChannelIDs: Array, 17 | BypassUsers: Array, 18 | BypassRoles: Array, 19 | }), 20 | ); 21 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { Config as IConfig } from './Structures/Interfaces/index.js'; 2 | 3 | const Config: IConfig = { 4 | TOKEN: 'YOUR_TOKEN_HERE', 5 | Database: { 6 | MongoDB: 'MONGODB_URI_HERE', 7 | }, 8 | DevGuilds: [ 9 | { 10 | name: 'Vape Support', 11 | id: '952168682937798656', 12 | }, 13 | ], 14 | OwnerIds: [ 15 | { 16 | name: 'Treotty', 17 | id: '735504973504184380', 18 | }, 19 | ], 20 | AdminIds: [ 21 | { 22 | name: 'Treotty', 23 | id: '735504973504184380', 24 | }, 25 | ], 26 | APIs: [ 27 | { 28 | name: 'Perspective API Key', 29 | apiKey: 'PERSPECTIVE_API_KEY_HERE', 30 | }, 31 | ], 32 | }; 33 | 34 | export default Config; 35 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { BaseClient } from './Structures/Classes/Client.js'; 2 | 3 | const client = new BaseClient(); 4 | 5 | client.start(); 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "strict": true, 5 | "lib": [ 6 | "ESNext" 7 | ], 8 | "module": "ESNext", 9 | "moduleResolution": "Node", 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "target": "ESNext", 13 | "baseUrl": "src", 14 | "rootDir": "./src", 15 | "outDir": "./dist", 16 | "importHelpers": true, 17 | "skipLibCheck": true, 18 | "noImplicitAny": false, 19 | "noEmitOnError": true, 20 | "skipDefaultLibCheck": true, 21 | "allowSyntheticDefaultImports": true, 22 | "useUnknownInCatchVariables": false 23 | }, 24 | "exclude": [ 25 | "node_modules" 26 | ] 27 | } --------------------------------------------------------------------------------