├── .gitignore ├── .github ├── FUNDING.yml ├── dependabot.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── tsconfig.json ├── images ├── embed.png ├── embed_2.png ├── embeds.png └── cdcommands.png ├── .npmignore ├── src ├── Database │ ├── models │ │ ├── prefixes.js │ │ ├── required-roles.js │ │ ├── userLanguage.js │ │ ├── guildLanguage.js │ │ ├── disabled-commands.js │ │ ├── index.js │ │ └── cooldown.js │ └── database.js ├── Base │ ├── DefaultCommands │ │ ├── index.js │ │ ├── SetPrefix.js │ │ ├── Language.js │ │ ├── Categories.js │ │ ├── Commands.js │ │ ├── RequiredRoles.js │ │ └── Help.js │ ├── Feature.js │ ├── Event.js │ ├── json-schema │ │ ├── replacers.json │ │ └── embed.json │ ├── Handling │ │ ├── ArgumentValidator.js │ │ ├── FeatureHandler.js │ │ ├── Languages.json │ │ ├── CooldownHandler.js │ │ ├── CacheHandler.js │ │ └── MessageJSON.js │ ├── CDClient.js │ ├── Command.js │ └── Message.js ├── registry │ ├── Events.js │ └── Commands.js ├── Functions.js ├── index.d.ts ├── types │ └── helper.types.d.ts └── index.js ├── SUPPORTED_LANGS.md ├── TODO.md ├── package.json ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── README.md ├── LICENSE └── OLD-README.MD /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: _CreativeDevelopments 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "resolveJsonModule": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /images/embed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CreativeDevelopments/CDCommands/HEAD/images/embed.png -------------------------------------------------------------------------------- /images/embed_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CreativeDevelopments/CDCommands/HEAD/images/embed_2.png -------------------------------------------------------------------------------- /images/embeds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CreativeDevelopments/CDCommands/HEAD/images/embeds.png -------------------------------------------------------------------------------- /images/cdcommands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CreativeDevelopments/CDCommands/HEAD/images/cdcommands.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Directories 2 | .github/ 3 | images/ 4 | test/ 5 | 6 | # Files 7 | CODE_OF_CONDUCT.md 8 | CONTRIBUTING.md 9 | OLD-README.md 10 | SUPPORTED_LANGS.md 11 | TODO.md 12 | UPDATES.md -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: parse-ms 10 | -------------------------------------------------------------------------------- /src/Database/models/prefixes.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require("mongoose"); 2 | 3 | const prefixSystem = new Schema({ 4 | gId: { type: String, required: true }, 5 | prefix: { type: String, required: true }, 6 | }); 7 | 8 | module.exports = model("prefixes", prefixSystem); 9 | -------------------------------------------------------------------------------- /src/Base/DefaultCommands/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | categories: require("./Categories"), 3 | commands: require("./Commands"), 4 | help: require("./Help"), 5 | requiredroles: require("./RequiredRoles"), 6 | setprefix: require("./SetPrefix"), 7 | language: require("./Language"), 8 | }; 9 | -------------------------------------------------------------------------------- /src/Database/models/required-roles.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require("mongoose"); 2 | 3 | const requiredRoles = new Schema({ 4 | gId: { type: String, required: true }, 5 | requiredRoles: { type: [Object], required: false }, 6 | }); 7 | 8 | module.exports = model("required-roles", requiredRoles); 9 | -------------------------------------------------------------------------------- /src/Database/models/userLanguage.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require("mongoose"); 2 | 3 | const userLanguage = new Schema({ 4 | uId: { type: String, required: true }, 5 | language: { type: String, required: false, default: "en" }, 6 | }); 7 | 8 | module.exports = model("userLanguages", userLanguage); 9 | -------------------------------------------------------------------------------- /src/Database/models/guildLanguage.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require("mongoose"); 2 | 3 | const guildLanguage = new Schema({ 4 | gId: { type: String, required: true }, 5 | language: { type: String, required: false, default: "en" }, 6 | }); 7 | 8 | module.exports = model("guildLanguages", guildLanguage); 9 | -------------------------------------------------------------------------------- /SUPPORTED_LANGS.md: -------------------------------------------------------------------------------- 1 | ### Current List of Supported and soon to be supported **default** languages 2 | 3 | - English :white_check_mark: 4 | - Spanish :white_check_mark: 5 | - German :white_check_mark: 6 | - Portuguese :white_check_mark: 7 | - Croatian :white_check_mark: 8 | - Turkish :x: 9 | - Hindi :white_check_mark: 10 | - Swedish :x: 11 | -------------------------------------------------------------------------------- /src/Database/models/disabled-commands.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require("mongoose"); 2 | 3 | const disabledCommands = new Schema({ 4 | gId: { type: String, required: true }, 5 | commands: { type: [String], required: false }, 6 | categories: { type: [String], required: false }, 7 | }); 8 | 9 | module.exports = model("disabled-commands", disabledCommands); 10 | -------------------------------------------------------------------------------- /src/Database/models/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | database: require("../database"), 3 | cooldown: require("./cooldown"), 4 | prefixes: require("./prefixes"), 5 | disabledCommands: require("./disabled-commands"), 6 | requiredRoles: require("./required-roles"), 7 | guildLanguage: require("./guildLanguage"), 8 | userLanguage: require("./userLanguage"), 9 | }; 10 | -------------------------------------------------------------------------------- /src/Database/models/cooldown.js: -------------------------------------------------------------------------------- 1 | const { Schema, model } = require("mongoose"); 2 | 3 | const cooldownSystem = new Schema({ 4 | uId: { type: String, required: false }, 5 | type: { type: String, required: true }, 6 | name: { type: String, required: true }, 7 | cooldown: { type: Date, required: true }, 8 | }); 9 | 10 | module.exports = model("cd-cooldowns", cooldownSystem); 11 | -------------------------------------------------------------------------------- /src/Base/Feature.js: -------------------------------------------------------------------------------- 1 | module.exports = class Feature { 2 | /** 3 | * @private 4 | * @type {(client: import("./CDClient").CDClient) => unknown | Promise} 5 | */ 6 | _run; 7 | 8 | /** 9 | * @param {(client: import("./CDClient").CDClient) => unknown | Promise} run 10 | */ 11 | constructor(run) { 12 | this._run = run; 13 | } 14 | 15 | get run() { 16 | return this._run; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Stuff we need to do 2 | 3 | ## High Priority 4 | 5 | 8 | 9 |
10 | 11 | ## Medium Priority 12 | 13 | 17 | 18 |
19 | 20 | ## Low Priority 21 | 22 | 25 | 26 |
27 | 28 | ## Other 29 | -------------------------------------------------------------------------------- /src/Database/database.js: -------------------------------------------------------------------------------- 1 | const { connect } = require("mongoose"); 2 | const colors = require("colors"); 3 | 4 | /** @param {string} mongoURI */ 5 | module.exports = async function (mongoURI) { 6 | connect( 7 | mongoURI, 8 | { 9 | useFindAndModify: false, 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true, 12 | }, 13 | (err) => { 14 | if (err) throw err; 15 | else 16 | console.log( 17 | `${colors.brightGreen("[DATABASE]")}`.white + 18 | colors.white(" Successfully connected to the database!"), 19 | ); 20 | }, 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Feature Request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/Base/Event.js: -------------------------------------------------------------------------------- 1 | const { CDClient } = require("./CDClient"); 2 | /** 3 | * @template {keyof import("discord.js").ClientEvents} K 4 | */ 5 | 6 | module.exports = class Event { 7 | /** 8 | * @type {K} 9 | */ 10 | name; 11 | 12 | /** 13 | * @function 14 | * @param {CDClient} client 15 | * @returns {Promise} 16 | * 17 | * @type {(client: CDClient, ...args: import("discord.js").ClientEvents[K]) => Promise} 18 | */ 19 | run; 20 | 21 | /** 22 | * @param {K} name 23 | * @param {(client: CDClient, ...args: import("discord.js").ClientEvents[K]) => Promise} run 24 | */ 25 | constructor(name, run) { 26 | this.name = name; 27 | this.run = run; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/Base/json-schema/replacers.json: -------------------------------------------------------------------------------- 1 | { 2 | "COMMAND": null, 3 | "CATEGORY": null, 4 | "CLIENT_PERMISSIONS": null, 5 | "MEMBER_PERMISSIONS": null, 6 | "ROLES": null, 7 | "USAGE": null, 8 | "COOLDOWN": null, 9 | "ACTION": null, 10 | "COMMAND_CATEGORY": null, 11 | "PREFIX": null, 12 | "ROLE": null, 13 | "GUILD_NAME": null, 14 | "ISO_CODE": null, 15 | "PROVIDED_CODES": null, 16 | "USER_GUILD": null, 17 | "EMBED": { 18 | "TITLE": null, 19 | "TITLE_URL": null, 20 | "AUTHOR_NAME": null, 21 | "AUTHOR_URL": null, 22 | "COLOR": null, 23 | "FIELD_NAME": null, 24 | "FIELD_VALUE": null, 25 | "FIELD_INLINE": null, 26 | "FOOTER_TEXT": null, 27 | "FOOTER_URL": null, 28 | "TIMESTAMP": null, 29 | "THUMBNAIL_URL": null, 30 | "DESCRIPTION": null, 31 | "IMAGE": null 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdcommands", 3 | "version": "4.2.6", 4 | "description": "An advanced handler for Discord.js Bots with TypeScript and JavaScript support.", 5 | "main": "./src/index.js", 6 | "types": "./src/index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/CreativeDevelopments/CDCommands" 13 | }, 14 | "keywords": [ 15 | "discord.js", 16 | "handler", 17 | "eventhandler", 18 | "commandhandler" 19 | ], 20 | "author": "CreativeDevelopments", 21 | "license": "Apache-2.0", 22 | "bugs": { 23 | "url": "https://github.com/CreativeDevelopments/CDCommands/issues" 24 | }, 25 | "homepage": "https://docs.creativedevelopments.org/cdcommands", 26 | "funding": { 27 | "type": "patreon", 28 | "url": "https://patreon.com/_CreativeDevelopments" 29 | }, 30 | "dependencies": { 31 | "colors": "^1.4.0", 32 | "discord.js": "^12.5.3", 33 | "mongoose": "^5.11.15", 34 | "parse-ms": "^2.1.0", 35 | "ramda": "^0.27.1" 36 | } 37 | } -------------------------------------------------------------------------------- /src/Base/Handling/ArgumentValidator.js: -------------------------------------------------------------------------------- 1 | const { Message } = require("discord.js"); 2 | const { CDClient } = require("../CDClient"); 3 | 4 | module.exports = class ArgumentValidator { 5 | /** 6 | * @private 7 | * @type {({ message, args, client, prefix, language }: { message: Message; args: string[]; client: CDClient, prefix: string; language: keyof import("./Languages.json") }) => boolean | string | Promise} 8 | */ 9 | _validate; 10 | /** 11 | * @private 12 | * @type {({ error, client, message, prefix, args, language }: { error: string; client: CDClient; message: Message, prefix: string; args: string[]; language: keyof import("./Languages.json") }) => unknown | Promise} 13 | */ 14 | _onError; 15 | /** 16 | * @private 17 | * @type {(message: Message) => unknown | Promise} 18 | */ 19 | _onSuccess; 20 | 21 | /** 22 | * @public 23 | * @param {Object} param0 24 | * @param {({ message, args, client, prefix, language }: { message: Message; args: string[]; client: CDClient, prefix: string; language: keyof import("./Languages.json") }) => boolean | string | Promise} param0.validate 25 | * @param {({ error, client, message, prefix, args, language }: { error: string; client: CDClient; message: Message, prefix: string; args: string[]; language: keyof import("./Languages.json") }) => unknown | Promise} param0.onError 26 | * @param {(message: Message) => unknown | Promise=} param0.onSuccess 27 | */ 28 | constructor({ validate, onError, onSuccess }) { 29 | this._onError = onError; 30 | this._onSuccess = onSuccess; 31 | this._validate = validate; 32 | } 33 | 34 | get onError() { 35 | return this._onError; 36 | } 37 | get onSuccess() { 38 | return this._onSuccess; 39 | } 40 | get validate() { 41 | return this._validate; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/registry/Events.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path"); 2 | const Event = require("../Base/Event"); 3 | const { CDClient } = require("../Base/CDClient"); 4 | const { lstatSync, existsSync, readdirSync, mkdirSync } = require("fs"); 5 | 6 | /** 7 | * @param {string} eventsDir 8 | * @param {CDClient} client 9 | * @param {boolean} customMessageEvent 10 | */ 11 | async function Events(eventsDir, client, customMessageEvent) { 12 | let totalEvents = 0; 13 | if (!existsSync(join(require.main.path, eventsDir))) { 14 | client.logWarn({ 15 | data: `No "${eventsDir}" directory found! Creating one...`, 16 | }); 17 | mkdirSync(join(require.main.path, eventsDir), { recursive: true }); 18 | } 19 | const files = readdirSync(join(require.main.path, eventsDir)); 20 | for (const file of files) { 21 | if (lstatSync(join(require.main.path, eventsDir, file)).isDirectory()) 22 | totalEvents += await Events( 23 | `${join(eventsDir, file)}`, 24 | client, 25 | customMessageEvent, 26 | ); 27 | else { 28 | if (require.main.filename.endsWith(".js") && file.endsWith(".js")) { 29 | /** @type {Event} */ 30 | const event = require(join(require.main.path, eventsDir, file)); 31 | if (event.name === "message" && !customMessageEvent) continue; 32 | if (event.name === "ready") continue; 33 | if (!(event instanceof Event)) { 34 | client.logError({ 35 | data: `Event file ${require.main.path}\\${eventsDir}\\${file} is an invalid event. Please make sure all files are setup correctly`, 36 | }); 37 | continue; 38 | } 39 | totalEvents += 1; 40 | client.on(event.name, event.run.bind(null, client)); 41 | } else if ( 42 | require.main.filename.endsWith(".ts") && 43 | file.endsWith(".ts") && 44 | !file.endsWith(".d.ts") 45 | ) { 46 | /** @type {Event} */ 47 | const event = require(join(require.main.path, eventsDir, file)).default; 48 | if (event.name === "message" && !customMessageEvent) continue; 49 | if (event.name === "ready") continue; 50 | if (!(event instanceof Event)) { 51 | client.logError({ 52 | data: `Event file ${require.main.path}\\${eventsDir}\\${file} is an invalid event. Please make sure all files are setup correctly`, 53 | }); 54 | continue; 55 | } 56 | totalEvents += 1; 57 | client.on(event.name, event.run.bind(null, client)); 58 | } 59 | } 60 | } 61 | return totalEvents; 62 | } 63 | 64 | module.exports = Events; 65 | -------------------------------------------------------------------------------- /src/Base/json-schema/embed.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "type": "object", 4 | "definitions": { 5 | "embed": { 6 | "properties": { 7 | "title": { 8 | "type": "string", 9 | "description": "Usable replacers: \"{TITLE}\"" 10 | }, 11 | "url": { 12 | "type": "string", 13 | "description": "Usable replacers: \"{TITLE_URL}\"" 14 | }, 15 | "author": { 16 | "type": "object", 17 | "properties": { 18 | "name": { 19 | "type": "string", 20 | "description": "Usable replacers: \"{AUTHOR_NAME}\"" 21 | }, 22 | "iconURL": { 23 | "type": "string", 24 | "description": "Usable replacers: \"{AUTHOR_URL}\"" 25 | } 26 | } 27 | }, 28 | "color": { 29 | "type": ["number", "string"], 30 | "description": "Usable replacers: \"{COLOR}\"" 31 | }, 32 | "fields": { 33 | "type": "array", 34 | "minItems": 1, 35 | "maxItems": 25, 36 | "items": { 37 | "type": "object", 38 | "properties": { 39 | "name": { 40 | "type": "string", 41 | "description": "Usable replacers: \"{FIELD_NAME}\"" 42 | }, 43 | "value": { 44 | "type": "string", 45 | "description": "Usable replacers: \"{FIELD_VALUE}\"" 46 | }, 47 | "inline": { 48 | "type": ["boolean", "string"], 49 | "description": "Usabled replacers: \"{FIELD_INLINE}\"" 50 | } 51 | } 52 | } 53 | }, 54 | "footer": { 55 | "type": "object", 56 | "properties": { 57 | "text": { 58 | "type": "string", 59 | "description": "Usabled replacers: \"{FOOTER_TEXT}\"" 60 | }, 61 | "iconURL": { 62 | "type": "string", 63 | "description": "Usabled replacers: \"{FOOTER_URL}\"" 64 | } 65 | } 66 | }, 67 | "timestamp": { 68 | "type": ["number", "string"], 69 | "description": "Usabled replacers: \"{TIMESTAMP}\"" 70 | }, 71 | "thumbnail": { 72 | "type": "object", 73 | "properties": { 74 | "url": { 75 | "type": "string", 76 | "description": "Usabled replacers: \"{THUMBNAIL_URL}\"" 77 | } 78 | } 79 | }, 80 | "description": { 81 | "type": "string", 82 | "description": "Usabled replacers: \"{DESCRIPTION}\"" 83 | }, 84 | "image": { 85 | "type": "object", 86 | "properties": { 87 | "url": { 88 | "type": "string", 89 | "description": "Usabled replacers: \"{IMAGE}\"" 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Base/Handling/FeatureHandler.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path"); 2 | const Feature = require("../Feature"); 3 | const { readdirSync, mkdirSync } = require("fs"); 4 | 5 | module.exports = class FeatureHandler { 6 | /** 7 | * @private 8 | * @type {import("../CDClient").CDClient} 9 | */ 10 | _client; 11 | 12 | /** 13 | * @private 14 | * @type {string} 15 | */ 16 | _dir; 17 | /** 18 | * @param {import("../CDClient").CDClient} client 19 | * @param {string=} dir 20 | */ 21 | constructor(client, dir) { 22 | this._client = client; 23 | this._dir = dir; 24 | 25 | const { path } = require.main; 26 | if (dir && dir !== "") this._dir = join(path, dir); 27 | 28 | try { 29 | readdirSync(this._dir); 30 | } catch (err) { 31 | this._client.logWarn({ 32 | data: `No "${dir}" directory found! Creating one...`, 33 | }); 34 | mkdirSync(this._dir); 35 | } 36 | 37 | this._init(); 38 | } 39 | 40 | /** @private */ 41 | _init() { 42 | const files = readdirSync(this._dir); 43 | for (const file of files) { 44 | if (require.main.filename.endsWith(".js") && file.endsWith(".js")) { 45 | /** @type {Feature} */ 46 | const feature = require(join(this._dir, file)); 47 | if (!(feature instanceof Feature)) { 48 | this._client.logError({ 49 | data: `Feature at path ${join( 50 | this._dir, 51 | file, 52 | )} is not a valid feature. Please make sure it is set up correctly.`, 53 | }); 54 | continue; 55 | } 56 | 57 | if (!feature.run || typeof feature.run !== "function") { 58 | this._client.logError({ 59 | data: `Feature at path ${join( 60 | this._dir, 61 | file, 62 | )} does not have a valid "run" function. Please make sure it is set up correctly.`, 63 | }); 64 | continue; 65 | } 66 | 67 | feature.run(this._client); 68 | } else if ( 69 | require.main.filename.endsWith(".ts") && 70 | file.endsWith(".ts") && 71 | !file.endsWith(".d.ts") 72 | ) { 73 | /** @type {Feature} */ 74 | const feature = require(join(this._dir, file)).default; 75 | if (!(feature instanceof Feature)) { 76 | this._client.logError({ 77 | data: `Feature at path ${join( 78 | this._dir, 79 | file, 80 | )} is not a valid feature. Please make sure it is set up correctly.`, 81 | }); 82 | continue; 83 | } 84 | 85 | if (!feature.run || typeof feature.run !== "function") { 86 | this._client.logError({ 87 | data: `Feature at path ${join( 88 | this._dir, 89 | file, 90 | )} does not have a valid "run" function. Please make sure it is set up correctly.`, 91 | }); 92 | continue; 93 | } 94 | 95 | feature.run(this._client); 96 | } 97 | } 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /src/Base/CDClient.js: -------------------------------------------------------------------------------- 1 | const { Collection, Client, Message, MessageEmbed } = require("discord.js"); 2 | const { Model } = require("mongoose"); 3 | const Cache = require("./Handling/CacheHandler"); 4 | const Cooldowns = require("./Handling/CooldownHandler"); 5 | const MessageJSON = require("./Handling/MessageJSON"); 6 | 7 | /** 8 | * @extends {Client} 9 | */ 10 | class CDClient extends Client { 11 | /** @type {Collection} */ 12 | commands; 13 | /** @type {Collection} */ 14 | aliases; 15 | /** @type {string} */ 16 | defaultPrefix; 17 | /** 18 | * @type {Cache<{ 19 | * cooldowns: { 20 | * model: Model<{ 21 | * uId: string; 22 | * type: string; 23 | * name: string; 24 | * cooldown: string; 25 | * }>; 26 | * getBy: string; 27 | * }; 28 | * disabledcommands: { 29 | * model: Model<{ 30 | * gId: string; 31 | * commands: string[]; 32 | * categories: string[]; 33 | * }>; 34 | * getBy: string; 35 | * }; 36 | * prefixes: { 37 | * model: Model<{ 38 | * gId: string; 39 | * prefix: string; 40 | * }>; 41 | * getBy: string; 42 | * }; 43 | * requriedroles: { 44 | * model: Model<{ 45 | * gId: string; 46 | * requiredRoles: object[]; 47 | * }>; 48 | * getBy: string; 49 | * }; 50 | * guildLanguage: { 51 | * model: Model<{ 52 | * gId: string; 53 | * language: string; 54 | * }>; 55 | * getBy: string; 56 | * }; 57 | * userLanguage: { 58 | * model: Model<{ 59 | * uId: string; 60 | * language: string; 61 | * }>; 62 | * getBy: string; 63 | * }; 64 | * }>} 65 | */ 66 | databaseCache; 67 | /** @type {MessageJSON} */ 68 | defaultResponses; 69 | /** @type {Cooldowns} */ 70 | cooldowns; 71 | /** @type {string[]} */ 72 | developers; 73 | /** @type {string[]} */ 74 | testservers; 75 | /** @type {boolean} */ 76 | ignoreBots; 77 | 78 | /** @type {({ guildId, authorId }: { guildId: string, authorId: string }) => keyof import("./Handling/Languages.json"))} */ 79 | getLanguage; 80 | 81 | /** @type {({ msg, data }: { msg: Message; data: string }) => MessageEmbed;} */ 82 | error; 83 | /** @type {({ msg, data }: { msg: Message; data: string }) => MessageEmbed;} */ 84 | load; 85 | /** @type {({ msg, data }: { msg: Message; data: string }) => MessageEmbed;} */ 86 | success; 87 | /** @type {({ msg, data }: { msg: Message; data: string }) => MessageEmbed;} */ 88 | info; 89 | /** @type {({ data }: { data: string }) => void;} */ 90 | logReady; 91 | /** @type {({ data }: { data: string }) => void;} */ 92 | logInfo; 93 | /** @type {({ data }: { data: string }) => void;} */ 94 | logError; 95 | /** @type {({ data }: { data: string }) => void;} */ 96 | logWarn; 97 | /** @type {({ data }: { data: string }) => void;} */ 98 | logDatabase; 99 | } 100 | 101 | module.exports.CDClient = CDClient; 102 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We are looking for people to contribute to CDCommands. 4 | If you would like to contribute please read below. 5 | Anyone who does contribute if you would like the "Contributor" role in our Support Server please let us know (If your GitHub name is not similar to your Discord name we may not know who it was). 6 | Thank you in advance to anyone who is thinking of Contributing. 7 | 8 | ## Adding Supported Languages 9 | 10 | If you would like to contribute by adding more languages, please first check to see if the language is already supported. 11 | You can do so by visiting [here](https://docs.creativedevelopments.org/cdcommands/development/supported-languages). 12 | If the language has not already been added please follow the steps below. 13 | 14 | 1. Fork our repository, you can do that [here](https://github.com/CreativeDevelopments/CDCommands). 15 | 2. Go to [src/Base/message.json](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/message.json) and copy from line [2](https://github.com/CreativeDevelopments/CDCommands/blob/3a2bb9cd0136962f5dae34addbc74e145e451dd6/src/Base/message.json#L2) all the way to line [237](https://github.com/CreativeDevelopments/CDCommands/blob/3a2bb9cd0136962f5dae34addbc74e145e451dd6/src/Base/message.json#L237). 16 | 3. Go to line [1423](https://github.com/CreativeDevelopments/CDCommands/blob/3a2bb9cd0136962f5dae34addbc74e145e451dd6/src/Base/message.json#L1423) (Note, this may change when more languages are added) and then add a , and make a new line to paste it in. 17 | 4. Change "en" to the [ISO 639-1 Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) of the language you are adding. 18 | 5. Translate everything in the "description". **DO NOT** translate the words inside the curly braces `{}`. For example where it says `{COMMAND} can only be used in a server`. You only translate the `can only be used in a server`, you can change the position of `{COMMAND}` so that it makes sense. 19 | 6. Once that file is done go to [src/Base/json-schema/message.json](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/json-schema/message.json) and copy line [11](https://github.com/CreativeDevelopments/CDCommands/blob/3a2bb9cd0136962f5dae34addbc74e145e451dd6/src/Base/json-schema/message.json#L11) all the way to line [454](https://github.com/CreativeDevelopments/CDCommands/blob/3a2bb9cd0136962f5dae34addbc74e145e451dd6/src/Base/json-schema/message.json#L454) 20 | 7. Go to line [2678](https://github.com/CreativeDevelopments/CDCommands/blob/3a2bb9cd0136962f5dae34addbc74e145e451dd6/src/Base/json-schema/message.json#L2679) (Note, this may change when more languages are added) and then add a `,` and make a new line to paste it in. 21 | 8. Translate everything in the `"examples"` and `"description"`. The `"examples"` are the same as the descriptions in the previous file, the `"descriptions"` are new phrases. Again, DO NOT translate the words in the curly braces `{}`. 22 | 9. Once everything is translated you can open a pull request for us to add it 23 | 24 | ## Other Contributions 25 | 26 | If you would like to contribute in any other way, i.e. making extra functions, fixing bugs etc. Please fork the repository, make your changes then open a pull request. 27 | -------------------------------------------------------------------------------- /src/Base/DefaultCommands/SetPrefix.js: -------------------------------------------------------------------------------- 1 | const Command = require("../Command"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const prefixes = require("../../Database/models/prefixes"); 4 | const ArgumentValidator = require("../Handling/ArgumentValidator"); 5 | 6 | module.exports = new Command({ 7 | aliases: ["updateprefix"], 8 | botPermissions: ["SEND_MESSAGES"], 9 | cooldown: 3000, 10 | description: "Set the Prefix of the bot", 11 | details: "Update or reset the prefix of the bot in the current server", 12 | devOnly: false, 13 | dmOnly: false, 14 | globalCooldown: 0, 15 | guildOnly: true, 16 | maxArgs: Infinity, 17 | minArgs: 1, 18 | name: "setprefix", 19 | noDisable: true, 20 | nsfw: false, 21 | testOnly: false, 22 | usage: "{prefix}setprefix ", 23 | userPermissions: ["MANAGE_GUILD"], 24 | category: "configuration", 25 | validator: new ArgumentValidator({ 26 | validate: ({ prefix, args }) => { 27 | if (args.join(" ").trim() === prefix) return "SAME_PREFIX"; 28 | }, 29 | onError: ({ message, client, error, language }) => { 30 | if (error === "SAME_PREFIX") { 31 | const res = client.defaultResponses.getValue( 32 | language, 33 | "PREFIX_COMMAND", 34 | "SAME_PREFIX", 35 | client.defaultResponses.fileData[language].PREFIX_COMMAND.SAME_PREFIX 36 | .embed 37 | ? { 38 | description: [], 39 | } 40 | : [], 41 | ); 42 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 43 | else message.channel.send(res); 44 | } 45 | }, 46 | }), 47 | run: async ({ args, client, message, prefix, language }) => { 48 | let prefixDoc = client.databaseCache.getDocument( 49 | "prefixes", 50 | message.guild.id, 51 | ); 52 | if (!prefixDoc) prefixDoc = new prefixes({ gId: message.guild.id, prefix }); 53 | 54 | const updatedPrefix = args.join(" ").trim(); 55 | if (updatedPrefix === prefix) { 56 | } 57 | 58 | prefixDoc.prefix = updatedPrefix; 59 | 60 | if (client.databaseCache.getDocument("prefixes", message.guild.id)) 61 | client.databaseCache.updateDocument("prefixes", prefixDoc); 62 | else client.databaseCache.insertDocument("prefixes", prefixDoc); 63 | 64 | const successRes = client.defaultResponses.getValue( 65 | language, 66 | "PREFIX_COMMAND", 67 | "SUCCESS", 68 | client.defaultResponses.fileData[language].PREFIX_COMMAND.SUCCESS.embed 69 | ? { 70 | description: [ 71 | { 72 | key: "GUILD_NAME", 73 | replace: message.guild.name, 74 | }, 75 | { 76 | key: "PREFIX", 77 | replace: `\`${updatedPrefix}\``, 78 | }, 79 | ], 80 | } 81 | : [ 82 | { 83 | key: "GUILD_NAME", 84 | replace: message.guild.name, 85 | }, 86 | { 87 | key: "PREFIX", 88 | replace: `\`${updatedPrefix}\``, 89 | }, 90 | ], 91 | ); 92 | if (successRes instanceof MessageEmbed) 93 | return message.channel.send({ embed: successRes }); 94 | else return message.channel.send(successRes); 95 | }, 96 | }); 97 | -------------------------------------------------------------------------------- /src/Base/Handling/Languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "aa": null, 3 | "ab": null, 4 | "ae": null, 5 | "af": null, 6 | "ak": null, 7 | "am": null, 8 | "an": null, 9 | "ar": null, 10 | "as": null, 11 | "av": null, 12 | "ay": null, 13 | "az": null, 14 | "ba": null, 15 | "be": null, 16 | "bg": null, 17 | "bh": null, 18 | "bi": null, 19 | "bm": null, 20 | "bn": null, 21 | "bo": null, 22 | "br": null, 23 | "bs": null, 24 | "ca": null, 25 | "ce": null, 26 | "ch": null, 27 | "co": null, 28 | "cr": null, 29 | "cs": null, 30 | "cu": null, 31 | "cv": null, 32 | "cy": null, 33 | "da": null, 34 | "de": null, 35 | "dv": null, 36 | "dz": null, 37 | "ee": null, 38 | "el": null, 39 | "en": null, 40 | "eo": null, 41 | "es": null, 42 | "et": null, 43 | "eu": null, 44 | "fa": null, 45 | "ff": null, 46 | "fi": null, 47 | "fj": null, 48 | "fo": null, 49 | "fr": null, 50 | "fy": null, 51 | "ga": null, 52 | "gd": null, 53 | "gl": null, 54 | "gn": null, 55 | "gu": null, 56 | "gv": null, 57 | "ha": null, 58 | "he": null, 59 | "hi": null, 60 | "ho": null, 61 | "hr": null, 62 | "ht": null, 63 | "hu": null, 64 | "hy": null, 65 | "hz": null, 66 | "ia": null, 67 | "id": null, 68 | "ie": null, 69 | "ig": null, 70 | "ii": null, 71 | "ik": null, 72 | "io": null, 73 | "is": null, 74 | "it": null, 75 | "iu": null, 76 | "ja": null, 77 | "jv": null, 78 | "ka": null, 79 | "kg": null, 80 | "ki": null, 81 | "kj": null, 82 | "kk": null, 83 | "kl": null, 84 | "km": null, 85 | "kn": null, 86 | "ko": null, 87 | "kr": null, 88 | "ks": null, 89 | "ku": null, 90 | "kv": null, 91 | "kw": null, 92 | "ky": null, 93 | "la": null, 94 | "lb": null, 95 | "lg": null, 96 | "li": null, 97 | "ln": null, 98 | "lo": null, 99 | "lt": null, 100 | "lu": null, 101 | "lv": null, 102 | "mg": null, 103 | "mh": null, 104 | "mi": null, 105 | "mk": null, 106 | "ml": null, 107 | "mn": null, 108 | "mr": null, 109 | "ms": null, 110 | "mt": null, 111 | "my": null, 112 | "na": null, 113 | "nb": null, 114 | "nd": null, 115 | "ne": null, 116 | "ng": null, 117 | "nl": null, 118 | "nn": null, 119 | "no": null, 120 | "nr": null, 121 | "nv": null, 122 | "ny": null, 123 | "oc": null, 124 | "oj": null, 125 | "om": null, 126 | "or": null, 127 | "os": null, 128 | "pa": null, 129 | "pi": null, 130 | "pl": null, 131 | "ps": null, 132 | "pt": null, 133 | "qu": null, 134 | "rm": null, 135 | "rn": null, 136 | "ro": null, 137 | "ru": null, 138 | "rw": null, 139 | "sa": null, 140 | "sc": null, 141 | "sd": null, 142 | "se": null, 143 | "sg": null, 144 | "si": null, 145 | "sk": null, 146 | "sl": null, 147 | "sm": null, 148 | "sn": null, 149 | "so": null, 150 | "sq": null, 151 | "sr": null, 152 | "ss": null, 153 | "st": null, 154 | "su": null, 155 | "sv": null, 156 | "sw": null, 157 | "ta": null, 158 | "te": null, 159 | "tg": null, 160 | "th": null, 161 | "ti": null, 162 | "tk": null, 163 | "tl": null, 164 | "tn": null, 165 | "to": null, 166 | "tr": null, 167 | "ts": null, 168 | "tt": null, 169 | "tw": null, 170 | "ty": null, 171 | "ug": null, 172 | "uk": null, 173 | "ur": null, 174 | "uz": null, 175 | "ve": null, 176 | "vi": null, 177 | "vo": null, 178 | "wa": null, 179 | "wo": null, 180 | "xh": null, 181 | "yi": null, 182 | "yo": null, 183 | "za": null, 184 | "zh": null, 185 | "zu": null 186 | } 187 | -------------------------------------------------------------------------------- /src/registry/Commands.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path"); 2 | const Command = require("../Base/Command"); 3 | const { existsSync, readdirSync, lstatSync, mkdirSync } = require("fs"); 4 | /** 5 | * @param {string} commandsDir 6 | * @param {import("../Base/CDClient").CDClient} client 7 | * @param {boolean} customHelpCommand 8 | * @returns {import("../Base/CDClient").CDClient} 9 | */ 10 | async function Commands(commandsDir, client, customHelpCommand) { 11 | if (!existsSync(join(require.main.path, commandsDir))) { 12 | client.logWarn({ 13 | data: `No "${commandsDir}" directory found! Creating one...`, 14 | }); 15 | mkdirSync(join(require.main.path, commandsDir), { recursive: true }); 16 | } 17 | const folders = readdirSync(join(require.main.path, commandsDir)); 18 | for (const folder of folders) { 19 | if (lstatSync(join(require.main.path, commandsDir, folder)).isDirectory()) 20 | await Commands(`${join(commandsDir, folder)}`, client, customHelpCommand); 21 | else { 22 | if (require.main.filename.endsWith(".js") && folder.endsWith(".js")) { 23 | /** @type {Command} */ 24 | const command = require(join(require.main.path, commandsDir, folder)); 25 | if (command.name === "help" && !customHelpCommand) continue; 26 | if (client.commands.get(command.name)) { 27 | client.logError({ 28 | data: `Command ${command.name} has occured more than once. Please make sure you have unique "name" properties.`, 29 | }); 30 | continue; 31 | } 32 | if (!(command instanceof Command)) { 33 | client.logError({ 34 | data: `Command file ${join( 35 | require.main.path, 36 | commandsDir, 37 | folder, 38 | )} is an invalid command. Please make sure all files are setup correctly.`, 39 | }); 40 | continue; 41 | } 42 | client.commands.set(command.name, command); 43 | if (command.aliases && command.aliases.length > 0) 44 | for (const alias of command.aliases) { 45 | if (client.aliases.get(alias)) 46 | client.logError({ 47 | data: `Alias ${alias} has occured more than once. Please make sure you have unique "aliases" properties.`, 48 | }); 49 | client.aliases.set(alias, command.name); 50 | } 51 | 52 | if (command.init !== undefined) command.init(client); 53 | } else if ( 54 | require.main.filename.endsWith(".ts") && 55 | folder.endsWith(".ts") && 56 | !folder.endsWith(".d.ts") 57 | ) { 58 | /** @type {Command} */ 59 | const command = require(join(require.main.path, commandsDir, folder)) 60 | .default; 61 | if (command.name === "help" && !customHelpCommand) continue; 62 | if (client.commands.get(command.name)) { 63 | client.logError({ 64 | data: `Command ${command.name} has occured more than once. Please make sure you have unique "name" properties.`, 65 | }); 66 | continue; 67 | } 68 | if (!(command instanceof Command)) { 69 | client.logError({ 70 | data: `Command file ${join( 71 | require.main.path, 72 | commandsDir, 73 | folder, 74 | )} is an invalid command. Please make sure all files are setup correctly.`, 75 | }); 76 | continue; 77 | } 78 | client.commands.set(command.name, command); 79 | if (command.aliases && command.aliases.length > 0) 80 | for (const alias of command.aliases) { 81 | if (client.aliases.get(alias)) 82 | client.logError({ 83 | data: `Alias ${alias} has occured more than once. Please make sure you have unique "aliases" properties.`, 84 | }); 85 | client.aliases.set(alias, command.name); 86 | } 87 | 88 | if (command.init !== undefined) command.init(client); 89 | } 90 | } 91 | } 92 | return client; 93 | } 94 | 95 | module.exports = Commands; 96 | -------------------------------------------------------------------------------- /src/Functions.js: -------------------------------------------------------------------------------- 1 | const { GuildMember } = require("discord.js"); 2 | const { Document } = require("mongoose"); 3 | const parseMilliseconds = require("parse-ms"); 4 | const Command = require("./Base/Command"); 5 | 6 | /** 7 | * @param {import("discord.js").PermissionResolvable[]} memberPermissions 8 | * @param {import("discord.js").PermissionResolvable[]} requiredPermissions 9 | * @returns {{perms: string | null; length: number}} 10 | */ 11 | function ValidatePermissions(memberPermissions, requiredPermissions) { 12 | /** @type {import("discord.js").PermissionResolvable[]} */ 13 | const missingPerms = requiredPermissions.filter( 14 | (perm) => !memberPermissions.includes(perm), 15 | ); 16 | 17 | return { 18 | perms: 19 | missingPerms.length > 0 20 | ? missingPerms 21 | .map((p, i, a) => 22 | a.length > 1 23 | ? i === a.length - 1 24 | ? `, and ${ProperCase(p.split("_").join(" "))}` 25 | : i === 0 26 | ? ProperCase(p.split("_").join(" ")) 27 | : `, ${ProperCase(p.split("_").join(" "))}` 28 | : ProperCase(p.split("_").join(" ")), 29 | ) 30 | .join("") 31 | : null, 32 | length: missingPerms.length, 33 | }; 34 | } 35 | /** 36 | * @param {string} string 37 | * @returns {string} 38 | */ 39 | function ProperCase(string) { 40 | return string.toLowerCase().replace(/(\b\w)/gi, (w) => w.toUpperCase()); 41 | } 42 | /** 43 | * @param {Document} rolesDocument 44 | * @param {GuildMember} member 45 | * @param {Command} command 46 | */ 47 | function ValidateRoles(rolesDocument, member, command) { 48 | const memberRoles = member.roles.cache 49 | .array() 50 | .map((r) => r.id) 51 | .filter((s) => s !== member.guild.id); 52 | const roles = rolesDocument.requiredRoles.filter( 53 | (ob) => ob.command === command.name, 54 | )[0]; 55 | if (roles) { 56 | const reqRoles = roles.roles; 57 | /** @type {string[]} */ 58 | const missingRoles = reqRoles.filter( 59 | (reqRole) => !memberRoles.includes(reqRole), 60 | ); 61 | if (missingRoles.length) 62 | return { 63 | roles: missingRoles 64 | .map((s, i, a) => 65 | a.length > 1 66 | ? i === a.length - 1 67 | ? `and ${member.guild.roles.cache.get(s).name}` 68 | : `${member.guild.roles.cache.get(s).name}, ` 69 | : member.guild.roles.cache.get(s).name, 70 | ) 71 | .join(""), 72 | length: missingRoles.length, 73 | }; 74 | } 75 | } 76 | 77 | /** @param {import("discord.js").PermissionResolvable[]} permissions */ 78 | function FormatPerms(permissions) { 79 | return permissions.map((p) => ProperCase(p.replace(/_/g, " "))).join(", "); 80 | } 81 | 82 | /** 83 | * @param {number | 84 | * { 85 | * days: number; 86 | * hours: number; 87 | * minutes: number; 88 | * seconds: number; 89 | * milliseconds: number; 90 | * microseconds: number; 91 | * nanoseconds: number; 92 | * }} cooldown 93 | */ 94 | function FormatCooldown(cooldown) { 95 | const totalTime = 96 | typeof cooldown === "object" ? cooldown : parseMilliseconds(cooldown); 97 | if (totalTime.milliseconds > 0) 98 | totalTime.seconds += parseFloat((totalTime.milliseconds / 1000).toFixed(1)); 99 | /** 100 | * @type {["days", "hours", "minutes", "seconds", "milliseconds", "microseconds", "nanoseconds"]} 101 | */ 102 | const keys = Object.keys(totalTime); 103 | const importantTimes = keys.splice(0, 4); 104 | 105 | const arr = []; 106 | for (const key of importantTimes) { 107 | if (totalTime[key] > 0) 108 | arr.push( 109 | `${totalTime[key]} ${ 110 | totalTime[key] === 1 ? key.slice(0, key.length - 1) : key 111 | }`, 112 | ); 113 | } 114 | return arr.join(", "); 115 | } 116 | 117 | module.exports = { 118 | ValidatePermissions, 119 | ProperCase, 120 | ValidateRoles, 121 | FormatPerms, 122 | FormatCooldown, 123 | }; 124 | -------------------------------------------------------------------------------- /src/Base/Command.js: -------------------------------------------------------------------------------- 1 | const { Message } = require("discord.js"); 2 | const { CDClient } = require("./CDClient"); 3 | const Validator = require("./Handling/ArgumentValidator"); 4 | 5 | module.exports = class Command { 6 | /** @type {string} */ 7 | name; 8 | 9 | /** @type {string[]} */ 10 | aliases; 11 | 12 | /** @type {string} */ 13 | description; 14 | 15 | /** @type {string} */ 16 | details; 17 | 18 | /** @type {number} */ 19 | minArgs; 20 | 21 | /** @type {number} */ 22 | maxArgs; 23 | 24 | /** @type {string} */ 25 | usage; 26 | 27 | /** @type {boolean} */ 28 | guildOnly; 29 | 30 | /** @type {boolean} */ 31 | testOnly; 32 | 33 | /** @type {boolean} */ 34 | dmOnly; 35 | 36 | /** @type {boolean} */ 37 | nsfw; 38 | 39 | /** @type {boolean} */ 40 | devOnly; 41 | 42 | /** @type {Validator} */ 43 | validator; 44 | 45 | /** @type {number} */ 46 | cooldown; 47 | 48 | /** @type {number} */ 49 | globalCooldown; 50 | 51 | /** @type {boolean} */ 52 | noDisable; 53 | 54 | /** @type {import("discord.js").PermissionResolvable[]} */ 55 | userPermissions; 56 | 57 | /** @type {import("discord.js").PermissionResolvable[]} */ 58 | botPermissions; 59 | 60 | /** @type {string} */ 61 | category; 62 | 63 | /** @type {(client: CDClient) => Promise | unknown} */ 64 | init; 65 | 66 | /** @type {({ message, args, client, prefix, language }: { message: Message, args: string[]; client: CDClient; prefix: string; language: keyof import("./Handling/Languages.json")}) => Promise;} */ 67 | run; 68 | 69 | /** 70 | * @param {{ 71 | * name: string; 72 | * aliases?: string[]; 73 | * description: string; 74 | * details: string; 75 | * minArgs: number; 76 | * maxArgs: number; 77 | * usage: string; 78 | * guildOnly?: boolean; 79 | * testOnly?: boolean; 80 | * dmOnly?: boolean; 81 | * nsfw?: boolean; 82 | * devOnly?: boolean; 83 | * validator?: Validator; 84 | * cooldown?: string | number; 85 | * globalCooldown?: string | number; 86 | * noDisable: boolean; 87 | * userPermissions?: import("discord.js").PermissionResolvable[]; 88 | * botPermissions?: import("discord.js").PermissionResolvable[]; 89 | * category: string; 90 | * init?: (client: CDClient) => Promise | unknown; 91 | * run: ({ message, args, client, prefix, language }: { message: Message, args: string[]; client: CDClient; prefix: string; language: keyof import("./Handling/Languages.json")}) => Promise; 92 | *}} CommandOptions 93 | */ 94 | constructor({ 95 | name, 96 | aliases, 97 | description, 98 | details, 99 | minArgs, 100 | maxArgs, 101 | usage, 102 | guildOnly, 103 | testOnly, 104 | dmOnly, 105 | nsfw, 106 | validator, 107 | devOnly, 108 | cooldown, 109 | globalCooldown, 110 | noDisable, 111 | userPermissions, 112 | botPermissions, 113 | category, 114 | init, 115 | run, 116 | }) { 117 | if (!guildOnly) guildOnly = false; 118 | if (!testOnly) testOnly = false; 119 | if (!dmOnly) dmOnly = false; 120 | if (!nsfw) nsfw = false; 121 | if (!devOnly) devOnly = false; 122 | if (!aliases) aliases = []; 123 | if (!userPermissions) userPermissions = []; 124 | if (!botPermissions) botPermissions = []; 125 | if (!cooldown) cooldown = 0; 126 | if (!globalCooldown) globalCooldown = 0; 127 | 128 | this.aliases = aliases; 129 | this.botPermissions = botPermissions; 130 | this.cooldown = cooldown; 131 | this.description = description; 132 | this.details = details; 133 | this.devOnly = devOnly; 134 | this.dmOnly = dmOnly; 135 | this.globalCooldown = globalCooldown; 136 | this.guildOnly = guildOnly; 137 | this.maxArgs = maxArgs; 138 | this.minArgs = minArgs; 139 | this.category = category; 140 | this.name = name; 141 | this.validator = validator; 142 | this.usage = usage; 143 | this.testOnly = testOnly; 144 | this.nsfw = nsfw; 145 | this.noDisable = noDisable; 146 | this.userPermissions = userPermissions; 147 | this.init = init; 148 | this.run = run; 149 | } 150 | }; 151 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | support@creativedevelopments.org. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/Base/Handling/CooldownHandler.js: -------------------------------------------------------------------------------- 1 | const { Collection, User } = require("discord.js"); 2 | const { Document } = require("mongoose"); 3 | const cooldownDoc = require("../../Database/models/cooldown"); 4 | const parsems = require("parse-ms"); 5 | 6 | module.exports = class Cooldowns { 7 | /** 8 | * @private 9 | * @type {import("../CDClient").CDClient} 10 | */ 11 | _client; 12 | /** 13 | * @private 14 | * @type {Collection>} 15 | */ 16 | _cooldowns = new Collection(); 17 | 18 | /** 19 | * @private 20 | * @type {Collection} 21 | */ 22 | _globalCoolDowns = new Collection(); 23 | 24 | /** 25 | * @param {Array>} dbCooldowns 26 | * @param {import("../CDClient").CDClient} client 27 | */ 28 | constructor(dbCooldowns, client) { 29 | this._init(dbCooldowns); 30 | this._client = client; 31 | } 32 | 33 | /** 34 | * @private 35 | * @param {Array>} DBcooldowns 36 | */ 37 | _init(DBcooldowns) { 38 | for (const cldwn of DBcooldowns) { 39 | if (cldwn.type === "global") 40 | this._globalCoolDowns.set(cldwn.name, cldwn.cooldown); 41 | else { 42 | if (!this._cooldowns.get(cldwn.uId)) { 43 | this._cooldowns.set( 44 | cldwn.uId, 45 | new Collection().set(cldwn.name, cldwn.cooldown), 46 | ); 47 | } else this._cooldowns.get(cldwn.uId).set(cldwn.name, cldwn.cooldown); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * @public 54 | * @param {User} user 55 | * @param {string} command 56 | * @param {Date} cooldown 57 | * @param {"global" | "local"} type 58 | * @returns {void} 59 | */ 60 | setCooldown(user, command, cooldown, type) { 61 | if (type === "local") { 62 | if (this._cooldowns.get(user.id) === undefined) 63 | this._cooldowns.set(user.id, new Collection().set(command, cooldown)); 64 | else this._cooldowns.get(user.id).set(command, cooldown); 65 | if (Math.ceil(cooldown.valueOf() - Date.now()) / 1000 / 60 >= 5) 66 | this._client.databaseCache.insertDocument( 67 | "cooldowns", 68 | new cooldownDoc({ 69 | uId: user.id, 70 | name: command, 71 | cooldown, 72 | type: "local", 73 | }), 74 | ); 75 | } else if (type === "global") { 76 | this._globalCoolDowns.set(command, cooldown); 77 | if (Math.ceil(cooldown.valueOf() - Date.now()) / 1000 / 60 >= 5) 78 | this._client.databaseCache.insertDocument( 79 | "cooldowns", 80 | new cooldownDoc({ name: command, cooldown, type: "global" }), 81 | ); 82 | } else 83 | throw new TypeError( 84 | 'Invalid "type" parameter. Please ensure you pass "global" or "local"', 85 | ); 86 | } 87 | 88 | /** 89 | * @public 90 | * @param {User} user 91 | * @param {string} command 92 | * @param {"global" | "local"} type 93 | */ 94 | getRemainingCooldown(user, command, type) { 95 | if (type === "local") { 96 | if (this._cooldowns.get(user.id) === undefined) return undefined; 97 | if (this._cooldowns.get(user.id).get(command) === undefined) 98 | return undefined; 99 | const remainingTime = parsems( 100 | this._cooldowns.get(user.id).get(command).valueOf() - Date.now(), 101 | ); 102 | return remainingTime; 103 | } else if (type === "global") { 104 | if (this._globalCoolDowns.get(command) === undefined) return undefined; 105 | const remainingTime = parsems( 106 | this._globalCoolDowns.get(command).valueOf() - Date.now(), 107 | ); 108 | return remainingTime; 109 | } else 110 | throw new TypeError( 111 | 'Invalid "type" parameter. Please ensure you pass "global" or "local"', 112 | ); 113 | } 114 | 115 | /** 116 | * @public 117 | * @param {User} user 118 | * @param {string} command 119 | * @param {Date} cooldown 120 | * @param {"global" | "local"} type 121 | * @returns {Promise} 122 | */ 123 | async isOnCooldown(user, command, cooldown, type) { 124 | if (type === "local") { 125 | if ( 126 | this._cooldowns.get(user.id) !== undefined && 127 | this._cooldowns.get(user.id).get(command) !== undefined 128 | ) { 129 | const date = this._cooldowns.get(user.id).get(command); 130 | if (date.valueOf() > Date.now()) return true; 131 | else { 132 | this._cooldowns.get(user.id).delete(command); 133 | const c = await cooldownDoc.findOne({ 134 | uId: user.id, 135 | name: command, 136 | type: "local", 137 | }); 138 | 139 | if (c !== null) 140 | await this._client.databaseCache.deleteDocument( 141 | "cooldowns", 142 | command, 143 | c, 144 | ); 145 | 146 | return false; 147 | } 148 | } 149 | } else if (type === "global") { 150 | if (this._globalCoolDowns.get(command) !== undefined) { 151 | const date = this._globalCoolDowns.get(command); 152 | if (date.valueOf() > Date.now()) return true; 153 | else { 154 | this._globalCoolDowns.delete(command); 155 | const c = await cooldownDoc.findOne({ 156 | name: command, 157 | type: "global", 158 | }); 159 | 160 | if (c !== null) 161 | await this._client.databaseCache.deleteDocument( 162 | "cooldowns", 163 | command, 164 | c, 165 | ); 166 | return false; 167 | } 168 | } 169 | } else 170 | throw new TypeError( 171 | 'Invalid "type" parameter. Please ensure you pass "global" or "local"', 172 | ); 173 | } 174 | }; 175 | -------------------------------------------------------------------------------- /src/Base/DefaultCommands/Language.js: -------------------------------------------------------------------------------- 1 | const Command = require("../Command"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const userLanguage = require("../../Database/models/userLanguage"); 4 | const ArgumentValidator = require("../Handling/ArgumentValidator"); 5 | const guildLanguage = require("../../Database/models/guildLanguage"); 6 | const valid_codes = Object.keys(require("../Handling/Languages.json")); 7 | 8 | module.exports = new Command({ 9 | name: "language", 10 | aliases: ["lang"], 11 | category: "configuration", 12 | description: "Allows the configuration of the bot language", 13 | details: 14 | "Allows the guild owner/guild member to configure their desired language of the bot, if the bot has the requested language setup", 15 | devOnly: false, 16 | dmOnly: false, 17 | guildOnly: true, 18 | usage: "{prefix}language ", 19 | maxArgs: 1, 20 | minArgs: 1, 21 | noDisable: true, 22 | nsfw: false, 23 | testOnly: false, 24 | botPermissions: ["SEND_MESSAGES", "EMBED_LINKS"], 25 | userPermissions: ["SEND_MESSAGES", "EMBED_LINKS"], 26 | cooldown: 5000, 27 | globalCooldown: 0, 28 | validator: new ArgumentValidator({ 29 | validate: ({ args, client }) => { 30 | if (!valid_codes.includes(args[0])) return "INVALID_ISO_CODE"; 31 | if (!Object.keys(client.defaultResponses.fileData).includes(args[0])) 32 | return "UNPROVIDED_LANGUAGE"; 33 | }, 34 | onError: ({ args, client, message, error, language }) => { 35 | if (error === "INVALID_ISO_CODE") { 36 | let res = client.defaultResponses.getValue( 37 | language, 38 | "LANGUAGE_COMMAND", 39 | "INVALID_ISO_CODE", 40 | client.defaultResponses.fileData[language].LANGUAGE_COMMAND 41 | .INVALID_ISO_CODE.embed !== undefined 42 | ? { 43 | description: [{ key: "ISO_CODE", replace: args[0] }], 44 | } 45 | : [ 46 | { 47 | key: "ISO_CODE", 48 | replace: args[0], 49 | }, 50 | ], 51 | ); 52 | 53 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 54 | else message.channel.send(res); 55 | } else if (error === "UNPROVIDED_LANGUAGE") { 56 | let res = client.defaultResponses.getValue( 57 | language, 58 | "LANGUAGE_COMMAND", 59 | "UNPROVIDED_LANGUAGE", 60 | client.defaultResponses.fileData[language].LANGUAGE_COMMAND 61 | .UNPROVIDED_LANGUAGE.embed !== undefined 62 | ? { 63 | description: [ 64 | { key: "ISO_CODE", replace: args[0] }, 65 | { 66 | key: "PROVIDED_CODES", 67 | replace: Object.keys(client.defaultResponses.fileData).join( 68 | ", ", 69 | ), 70 | }, 71 | ], 72 | } 73 | : [ 74 | { 75 | key: "ISO_CODE", 76 | replace: args[0], 77 | }, 78 | { 79 | key: "PROVIDED_CODES", 80 | replace: Object.keys(client.defaultResponses.fileData).join( 81 | ", ", 82 | ), 83 | }, 84 | ], 85 | ); 86 | 87 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 88 | else message.channel.send(res); 89 | } 90 | }, 91 | }), 92 | async run({ args, client, message }) { 93 | const owner = 94 | message.author.id === 95 | (await message.guild.members.fetch(message.guild.owner.id)).id; 96 | if (owner) { 97 | const document = 98 | client.databaseCache.getDocument("guildLanguage", message.guild.id) || 99 | new guildLanguage({ 100 | gId: message.guild.id, 101 | }); 102 | 103 | document.language = args[0]; 104 | 105 | if (!client.databaseCache.getDocument("guildLanguage", message.guild.id)) 106 | client.databaseCache.insertDocument("guildLanguage", document); 107 | else client.databaseCache.updateDocument("guildLanguage", document); 108 | } else { 109 | const document = 110 | client.databaseCache.getDocument("userLanguage", message.author.id) || 111 | new userLanguage({ 112 | uId: message.author.id, 113 | }); 114 | 115 | document.language = args[0]; 116 | 117 | if (!client.databaseCache.getDocument("userLanguage", message.author.id)) 118 | client.databaseCache.insertDocument("userLanguage", document); 119 | else client.databaseCache.updateDocument("userLanguage", document); 120 | } 121 | 122 | const language = client.getLanguage({ 123 | guildId: message.guild.id, 124 | authorId: message.author.id, 125 | }); 126 | 127 | const successRes = client.defaultResponses.getValue( 128 | language, 129 | "LANGUAGE_COMMAND", 130 | "SUCCESS", 131 | client.defaultResponses.fileData[language].LANGUAGE_COMMAND.SUCCESS.embed 132 | ? { 133 | description: [ 134 | { 135 | key: "USER_GUILD", 136 | replace: owner ? message.guild.name : message.author.username, 137 | }, 138 | { 139 | key: "ISO_CODE", 140 | replace: `**${args[0]}**`, 141 | }, 142 | ], 143 | } 144 | : [ 145 | { 146 | key: "USER_GUILD", 147 | replace: owner ? message.guild.name : message.author.username, 148 | }, 149 | { 150 | key: "ISO_CODE", 151 | replace: `**${args[0]}**`, 152 | }, 153 | ], 154 | ); 155 | 156 | if (successRes instanceof MessageEmbed) 157 | return message.channel.send({ embed: successRes }); 158 | else return message.channel.send(successRes); 159 | }, 160 | }); 161 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Client, 3 | ClientEvents, 4 | PermissionResolvable, 5 | Message, 6 | } from "discord.js"; 7 | import { Model, Document } from "mongoose"; 8 | 9 | import { CDClient } from "./types/helper.types"; 10 | export class CDCommands { 11 | private _client: CDClient; 12 | private _commandsDir: string; 13 | private _eventsDir: string; 14 | private _featuresDir: string; 15 | private _testServers: string[]; 16 | private _devs: string[]; 17 | private _defaultPrefix: string; 18 | private _ignoreBots: string; 19 | private _mongoURI: string; 20 | private _customMessageEvent: boolean; 21 | private _disabledDefaultCommands: ( 22 | | "help" 23 | | "command" 24 | | "category" 25 | | "language" 26 | | "requiredroles" 27 | | "setprefix" 28 | )[]; 29 | private _cacheUpdateSpeed: number; 30 | 31 | public constructor( 32 | client: Client, 33 | options: { 34 | commandsDir?: string; 35 | eventsDir?: string; 36 | featuresDir?: string; 37 | testServers?: string[]; 38 | customMessageEvent?: boolean; 39 | MessageJSONPath?: string; 40 | disabledDefaultCommands?: Array< 41 | | "help" 42 | | "command" 43 | | "category" 44 | | "language" 45 | | "requiredroles" 46 | | "setprefix" 47 | >; 48 | devs?: string[]; 49 | defaultPrefix: string; 50 | mongoURI: string; 51 | ignoreBots?: boolean; 52 | cacheUpdateSpeed?: number; 53 | }, 54 | ); 55 | 56 | public readonly defaultPrefix: string; 57 | public readonly testServers: string[]; 58 | } 59 | 60 | export class Event { 61 | public name: T; 62 | public run: ( 63 | client: CDClient, 64 | ...args: ClientEvents[T] 65 | ) => Promise | unknown; 66 | 67 | public constructor( 68 | name: T, 69 | run: ( 70 | client: CDClient, 71 | ...args: ClientEvents[T] 72 | ) => Promise | unknown, 73 | ); 74 | } 75 | 76 | export class Command { 77 | public name: string; 78 | public aliases: string; 79 | public description: string; 80 | public details: string; 81 | public minArgs: number; 82 | public maxArgs: number; 83 | public usage: string; 84 | public guildOnly: boolean; 85 | public testOnly: boolean; 86 | public dmOnly: boolean; 87 | public nsfw: boolean; 88 | public devOnly: boolean; 89 | public cooldown: string | number; 90 | public globalCooldown: string | number; 91 | public noDisable: boolean; 92 | public userPermissions: PermissionResolvable[]; 93 | public botPermissions: PermissionResolvable[]; 94 | public category: string; 95 | public validator: Validator | undefined; 96 | 97 | public init: (client: CDClient) => Promise | unknown; 98 | public run: ({ 99 | message, 100 | args, 101 | client, 102 | prefix, 103 | language, 104 | }: { 105 | message: Message; 106 | args: string[]; 107 | client: CDClient; 108 | prefix: string; 109 | language: keyof typeof import("./Base/Handling/Languages.json"); 110 | }) => Promise | unknown; 111 | 112 | constructor(options: { 113 | name: string; 114 | aliases?: string[]; 115 | description: string; 116 | details: string; 117 | minArgs: number; 118 | maxArgs: number; 119 | usage: string; 120 | guildOnly?: boolean; 121 | testOnly?: boolean; 122 | dmOnly?: boolean; 123 | nsfw?: boolean; 124 | devOnly?: boolean; 125 | cooldown?: string | number; 126 | globalCooldown?: string | number; 127 | noDisable?: boolean; 128 | userPermissions?: PermissionResolvable[]; 129 | botPermissions?: PermissionResolvable[]; 130 | category: string; 131 | validator?: Validator | undefined; 132 | init?: Command["init"]; 133 | run: Command["run"]; 134 | }); 135 | } 136 | 137 | export class Feature { 138 | constructor(run: (client: CDClient) => unknown | Promise); 139 | } 140 | 141 | export class Validator { 142 | private _validate: ({ 143 | message, 144 | args, 145 | client, 146 | prefix, 147 | language, 148 | }: { 149 | message: Message; 150 | args: string[]; 151 | client: CDClient; 152 | prefix: string; 153 | language: keyof typeof import("./Base/Handling/Languages.json"); 154 | }) => boolean | string | Promise; 155 | private _onError: ({ 156 | error, 157 | client, 158 | message, 159 | prefix, 160 | args, 161 | language, 162 | }: { 163 | error: string; 164 | client: CDClient; 165 | message: Message; 166 | prefix: string; 167 | args: string[]; 168 | language: keyof typeof import("./Base/Handling/Languages.json"); 169 | }) => unknown | Promise; 170 | private _onSuccess: (message: Message) => unknown | Promise; 171 | 172 | constructor(options: { 173 | validate: Validator["_validate"]; 174 | onError: Validator["_onError"]; 175 | onSuccess?: Validator["_onSuccess"]; 176 | }); 177 | 178 | public readonly onError: Validator["_onError"]; 179 | public readonly onSuccess: Validator["_onSuccess"]; 180 | public readonly validate: Validator["_validate"]; 181 | } 182 | 183 | export const Models: { 184 | cooldown: Model< 185 | Document<{ 186 | uId: string; 187 | type: string; 188 | name: string; 189 | cooldown: string; 190 | }> 191 | >; 192 | disabledCommands: Model< 193 | Document<{ 194 | gId: string; 195 | commands: string[]; 196 | categories: string[]; 197 | }> 198 | >; 199 | prefixes: Model< 200 | Document<{ 201 | gId: string; 202 | prefix: string; 203 | }> 204 | >; 205 | requiredRoles: Model< 206 | Document<{ 207 | gId: string; 208 | requiredRoles: object[]; 209 | }> 210 | >; 211 | guildLanguage: Model< 212 | Document<{ 213 | gId: string; 214 | language: string; 215 | }> 216 | >; 217 | userLanguage: Model< 218 | Document<{ 219 | uId: string; 220 | language: string; 221 | }> 222 | >; 223 | }; 224 | -------------------------------------------------------------------------------- /src/types/helper.types.d.ts: -------------------------------------------------------------------------------- 1 | import { Client, MessageEmbed, Collection, Message, User } from "discord.js"; 2 | import { Model, Document } from "mongoose"; 3 | import { Parsed } from "parse-ms"; 4 | 5 | type MessageJSONEmbedArgs = [ 6 | { 7 | key: 8 | | keyof typeof import("../Base/json-schema/replacers.json")["EMBED"] 9 | | keyof typeof import("../Base/json-schema/replacers.json"); 10 | replace: string; 11 | }, 12 | ]; 13 | 14 | type MessageJSONStringArgs = [ 15 | { 16 | key: keyof typeof import("../Base/json-schema/replacers.json"); 17 | replace: string; 18 | }, 19 | ]; 20 | 21 | type MessageJSONArgs = 22 | | { 23 | title?: MessageJSONEmbedArgs; 24 | url?: MessageJSONEmbedArgs; 25 | author_name?: MessageJSONEmbedArgs; 26 | author_iconURL?: MessageJSONEmbedArgs; 27 | color?: MessageJSONEmbedArgs; 28 | fields?: [ 29 | { 30 | name?: MessageJSONEmbedArgs; 31 | value?: MessageJSONEmbedArgs; 32 | inline?: MessageJSONEmbedArgs; 33 | }, 34 | ]; 35 | footer_text?: MessageJSONEmbedArgs; 36 | footer_iconURL?: MessageJSONEmbedArgs; 37 | timestamp?: MessageJSONEmbedArgs; 38 | thumbnail_url?: MessageJSONEmbedArgs; 39 | description?: MessageJSONEmbedArgs; 40 | image_url?: MessageJSONEmbedArgs; 41 | } 42 | | MessageJSONStringArgs; 43 | 44 | export class Cache< 45 | T extends { 46 | [key: string]: { 47 | model: Model; 48 | getBy: string; 49 | }; 50 | } 51 | > { 52 | private _cache: Collection>>; 53 | private _updateSpeed: number; 54 | private _options: { models: T; updateSpeed: number }; 55 | 56 | public constructor(options: { models: T; updateSpeed: number }); 57 | 58 | private _init(): Promise; 59 | private _startUpdateCycle(): void; 60 | 61 | public getDocument(type: keyof T, findBy: string): Document; 62 | public insertDocument(type: keyof T, doc: Document): void; 63 | public updateDocument(type: keyof T, update: Document): void; 64 | public deleteDocument( 65 | type: keyof T, 66 | findBy: string, 67 | document: Document, 68 | ): Promise; 69 | } 70 | 71 | export class MessageJSON { 72 | private _path: string; 73 | private _fileData: T; 74 | 75 | public constructor(messagePath?: string); 76 | 77 | public getValue< 78 | V extends keyof T["en"], 79 | S extends keyof T["en"][V] extends "embed" 80 | ? "" 81 | : T["en"][V] extends string 82 | ? "" 83 | : keyof T["en"][V] 84 | >(key: V, secondary_key: S, args: MessageJSONArgs): MessageEmbed | string; 85 | } 86 | 87 | export class Cooldowns { 88 | private _client: CDClient; 89 | private _cooldowns: Collection>; 90 | private _globalCoolDowns: Collection; 91 | 92 | public constructor(dbCooldowns: Array>, client: CDClient); 93 | 94 | private _init(DBcooldowns: Array>): void; 95 | 96 | public setCooldown( 97 | user: User, 98 | command: string, 99 | cooldown: Date, 100 | type: "global" | "local", 101 | ): void; 102 | 103 | public getRemainingCooldown( 104 | user: User, 105 | command: string, 106 | type: "global" | "local", 107 | ): Parsed; 108 | 109 | public isOnCooldown( 110 | user: User, 111 | command: string, 112 | cooldown: Date, 113 | type: "global" | "local", 114 | ): boolean; 115 | } 116 | 117 | export class CDClient extends Client { 118 | public commands: Collection; 119 | public aliases: Collection; 120 | public defaultPrefix: string; 121 | public databaseCache: Cache<{ 122 | cooldowns: { 123 | model: Model; 124 | getBy: string; 125 | }; 126 | disabledcommands: { 127 | model: Model< 128 | Document<{ 129 | gId: string; 130 | commands: string[]; 131 | categories: string[]; 132 | }> 133 | >; 134 | getBy: string; 135 | }; 136 | prefixes: { 137 | model: Model< 138 | Document<{ 139 | gId: string; 140 | prefix: string; 141 | }> 142 | >; 143 | getBy: string; 144 | }; 145 | requriedroles: { 146 | model: Model< 147 | Document<{ 148 | gId: string; 149 | requiredRoles: object[]; 150 | }> 151 | >; 152 | getBy: string; 153 | }; 154 | guildLanguage: { 155 | model: Model< 156 | Document<{ 157 | gId: string; 158 | language: string; 159 | }> 160 | >; 161 | getBy: string; 162 | }; 163 | userLanguage: { 164 | model: Model< 165 | Document<{ 166 | uId: string; 167 | language: string; 168 | }> 169 | >; 170 | getBy: string; 171 | }; 172 | }>; 173 | public defaultResponses: MessageJSON; 174 | public cooldowns: Cooldowns; 175 | public developers: string[]; 176 | public testservers: string[]; 177 | public getLanguage: ({ 178 | guildId, 179 | authorId, 180 | }: { 181 | guildId: string; 182 | authorId: string; 183 | }) => keyof typeof import("../Base/Handling/Languages.json"); 184 | public error: ({ msg, data }: { msg: Message; data: string }) => MessageEmbed; 185 | public load: ({ msg, data }: { msg: Message; data: string }) => MessageEmbed; 186 | public success: ({ 187 | msg, 188 | data, 189 | }: { 190 | msg: Message; 191 | data: string; 192 | }) => MessageEmbed; 193 | public info: ({ msg, data }: { msg: Message; data: string }) => MessageEmbed; 194 | public logReady: ({ data }: { data: string }) => void; 195 | public logInfo: ({ data }: { data: string }) => void; 196 | public logError: ({ data }: { data: string }) => void; 197 | public logWarn: ({ data }: { data: string }) => void; 198 | public logDatabase: ({ data }: { data: string }) => void; 199 | constructor(); 200 | } 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | Logo 5 | 6 |

7 | 8 | 9 |
10 |

CDCommands

11 |

An advanced handler for Discord.js Bots with TypeScript and JavaScript support!

12 |
13 | 14 |
15 | 16 | 17 |

18 | 19 | Discord Server 20 | 21 | 22 | Downloads 23 | 24 | 25 | Monthly Downloads 26 | 27 | 28 | Version 29 | 30 | 31 | License 32 | 33 | 34 | Patreon 35 | 36 | 37 | Stars 38 | 39 |

40 | 41 | --- 42 | 43 | # We have now moved this over to our new [Guide](https://docs.creativedevelopments.org/cdcommands). 44 | 45 | ## Contents of the Guide 46 | 47 | - [Home Page](https://docs.creativedevelopments.org/cdcommands/) 48 | - [Changelog](https://docs.creativedevelopments.org/cdcommands/development/changelog) 49 | - [Supported Languages](https://docs.creativedevelopments.org/cdcommands/development/supported-languages) 50 | - [Contribute](https://docs.creativedevelopments.org/cdcommands/development/contribute) 51 | - [Issues and Feature Requests](https://docs.creativedevelopments.org/cdcommands/development/issues-and-features-requests) 52 | - [Installation](https://docs.creativedevelopments.org/cdcommands/getting-started/installation) 53 | - [Initial Setup](https://docs.creativedevelopments.org/cdcommands/getting-started/initial-setup) 54 | - [Creating a Basic Command](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/command-properties) 55 | - [Command Properties](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/command-properties) 56 | - [Argument Validation](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/argument-validation) 57 | - [Validate](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/argument-validation/the-validate-function) 58 | - [On Error](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/argument-validation/the-on-error-function) 59 | - [On Success](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/argument-validation/the-on-success-function) 60 | - [Default Commands](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/default-commands) 61 | - [Help Command](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/default-commands/help-command) 62 | - [Prefix Command](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/default-commands/prefix-command) 63 | - [Language Command](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/default-commands/language-command) 64 | - [Roles Command](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/default-commands/roles-command) 65 | - [Commands Command](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/default-commands/commands-command) 66 | - [Categories Command](https://docs.creativedevelopments.org/cdcommands/creating-a-basic-command/default-commands/categories-command) 67 | - [Creating an Event](https://docs.creativedevelopments.org/cdcommands/creating-events-and-features/creating-an-event) 68 | - [Creating a Feature](https://docs.creativedevelopments.org/cdcommands/creating-events-and-features/creating-a-feature) 69 | - [Default Responses](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses) 70 | - [Fetching Values](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/fetching-values) 71 | - [Embeds](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/embeds) 72 | - [Replacers](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/replacers) 73 | - [String Replacers](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/replacers/string-replacers) 74 | - [Embed Replacers](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/replacers/embed-replacers) 75 | - [Language Support](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/language-support) 76 | - [Changing Your Language](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/language-support/changing-your-language) 77 | - [Adding a New Language](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/language-support/adding-a-new-language) 78 | - [Dynamic Language Support](https://docs.creativedevelopments.org/cdcommands/message.json/default-responses/language-support/dynamic-language-support) 79 | - [Client Utils](https://docs.creativedevelopments.org/cdcommands/utils/client-utils) 80 | 81 | # Other 82 | 83 | If you have any suggestions, bugs or need some help setting it up please join our [Support Server](https://discord.com/invite/jUNbV5u). 84 | You can see what we are adding next on the [to do list](https://github.com/CreativeDevelopments/CDCommands/blob/main/TODO.md) on our GitHub repository. 85 | -------------------------------------------------------------------------------- /src/Base/DefaultCommands/Categories.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed } = require("discord.js"); 2 | const DisabledCommands = require("../../Database/models/disabled-commands"); 3 | const Command = require("../Command"); 4 | const ArgumentValidator = require("../Handling/ArgumentValidator"); 5 | 6 | module.exports = new Command({ 7 | aliases: ["categories"], 8 | botPermissions: ["SEND_MESSAGES"], 9 | cooldown: 3000, 10 | description: "Enable or disable categories", 11 | details: 12 | "Allows you to enable or disable entire categories of bot commands in the current server", 13 | devOnly: false, 14 | dmOnly: false, 15 | globalCooldown: 0, 16 | guildOnly: true, 17 | maxArgs: 2, 18 | minArgs: 2, 19 | name: "category", 20 | noDisable: true, 21 | nsfw: false, 22 | testOnly: false, 23 | usage: "{prefix}category ", 24 | userPermissions: ["MANAGE_GUILD"], 25 | category: "configuration", 26 | validator: new ArgumentValidator({ 27 | validate: ({ args, client }) => { 28 | const categories = new Set(client.commands.map((c) => c.category)); 29 | 30 | if (args[0] !== "enable" && args[0] !== "disable") 31 | return "INVALID_ARGS_0"; 32 | else if (!categories.has(args[1])) return "INVALID_ARGS_1"; 33 | }, 34 | onError: ({ client, message, error, prefix, args, language }) => { 35 | if (error === "INVALID_ARGS_0") { 36 | const res = client.defaultResponses.getValue( 37 | language, 38 | "CATEGORY_COMMAND", 39 | "INVALID_ARGS_ERROR", 40 | client.defaultResponses.fileData[language].CATEGORY_COMMAND 41 | .INVALID_ARGS_ERROR.embed 42 | ? { 43 | description: [ 44 | { 45 | key: "USAGE", 46 | replace: `\`${prefix}category \``, 47 | }, 48 | ], 49 | } 50 | : [ 51 | { 52 | key: "USAGE", 53 | replace: `\`${prefix}category \``, 54 | }, 55 | ], 56 | ); 57 | 58 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 59 | else message.channel.send(res); 60 | } else if (error === "INVALID_ARGS_1") { 61 | const res = client.defaultResponses.getValue( 62 | language, 63 | "CATEGORY_COMMAND", 64 | "NON_EXISTANT_CATEGORY", 65 | client.defaultResponses.fileData[language].CATEGORY_COMMAND 66 | .NON_EXISTANT_CATEGORY.embed 67 | ? { 68 | description: [ 69 | { 70 | key: "CATEGORY", 71 | replace: args[0], 72 | }, 73 | ], 74 | } 75 | : [ 76 | { 77 | key: "CATEGORY", 78 | replace: args[0], 79 | }, 80 | ], 81 | ); 82 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 83 | else message.channel.send(res); 84 | } 85 | }, 86 | }), 87 | run: ({ args, client, message, language }) => { 88 | let DisabledDoc = client.databaseCache.getDocument( 89 | "disabledcommands", 90 | message.guild.id, 91 | ); 92 | if (!DisabledDoc) 93 | DisabledDoc = new DisabledCommands({ 94 | gId: message.guild.id, 95 | commands: [], 96 | categories: [], 97 | }); 98 | 99 | const enabledDisabled = args[0].toLowerCase(); 100 | const categoryName = args[1]; 101 | 102 | if (enabledDisabled === "enable") { 103 | const res = client.defaultResponses.getValue( 104 | language, 105 | "CATEGORY_COMMAND", 106 | "ALREADY_ENABLED", 107 | client.defaultResponses.fileData[language].CATEGORY_COMMAND 108 | .ALREADY_ENABLED.embed 109 | ? { 110 | description: [ 111 | { 112 | key: "CATEGORY", 113 | replace: categoryName, 114 | }, 115 | ], 116 | } 117 | : [ 118 | { 119 | key: "CATEGORY", 120 | replace: categoryName, 121 | }, 122 | ], 123 | ); 124 | if (!DisabledDoc.categories.includes(categoryName)) { 125 | if (res instanceof MessageEmbed) 126 | return message.channel.send({ embed: res }); 127 | else return message.channel.send(res); 128 | } 129 | 130 | const i = DisabledDoc.categories.findIndex((v) => v === categoryName); 131 | DisabledDoc.categories.splice(i, 1); 132 | } else if (enabledDisabled === "disable") { 133 | const res = client.defaultResponses.getValue( 134 | language, 135 | "CATEGORY_COMMAND", 136 | "ALREADY_DISABLED", 137 | client.defaultResponses.fileData[language].CATEGORY_COMMAND 138 | .ALREADY_DISABLED.embed 139 | ? { 140 | description: [ 141 | { 142 | key: "CATEGORY", 143 | replace: categoryName, 144 | }, 145 | ], 146 | } 147 | : [ 148 | { 149 | key: "CATEGORY", 150 | replace: categoryName, 151 | }, 152 | ], 153 | ); 154 | if (DisabledDoc.categories.includes(categoryName)) { 155 | if (res instanceof MessageEmbed) 156 | return message.channel.send({ embed: res }); 157 | else return message.channel.send(res); 158 | } 159 | DisabledDoc.categories.push(categoryName); 160 | } 161 | 162 | if (!client.databaseCache.getDocument("disabledcommands", message.guild.id)) 163 | client.databaseCache.insertDocument("disabledcommands", DisabledDoc); 164 | else client.databaseCache.updateDocument("disabledcommands", DisabledDoc); 165 | 166 | const successRes = client.defaultResponses.getValue( 167 | language, 168 | "CATEGORY_COMMAND", 169 | "SUCCESS", 170 | client.defaultResponses.fileData[language].CATEGORY_COMMAND.SUCCESS.embed 171 | ? { 172 | description: [ 173 | { 174 | key: "ACTION", 175 | replace: `${enabledDisabled}d`, 176 | }, 177 | { 178 | key: "CATEGORY", 179 | replace: categoryName, 180 | }, 181 | ], 182 | } 183 | : [ 184 | { 185 | key: "ACTION", 186 | replace: `${enabledDisabled}d`, 187 | }, 188 | { 189 | key: "CATEGORY", 190 | replace: categoryName, 191 | }, 192 | ], 193 | ); 194 | 195 | if (successRes instanceof MessageEmbed) 196 | return message.channel.send({ embed: successRes }); 197 | else return message.channel.send(successRes); 198 | }, 199 | }); 200 | -------------------------------------------------------------------------------- /src/Base/Handling/CacheHandler.js: -------------------------------------------------------------------------------- 1 | const { Collection, User } = require("discord.js"); 2 | const { Document, Model } = require("mongoose"); 3 | const prefixes = require("../../Database/models/prefixes"); 4 | const cooldown = require("../../Database/models/cooldown"); 5 | const requiredRoles = require("../../Database/models/required-roles"); 6 | const disabledCommands = require("../../Database/models/disabled-commands"); 7 | /** 8 | * @template {{ 9 | * [key: string]: { 10 | * model: Model; 11 | * getBy: string; 12 | * } 13 | * }} T 14 | */ 15 | module.exports = class Cache { 16 | /** 17 | * @private 18 | * @type {Collection>>} 19 | */ 20 | _cache = new Collection(); 21 | /** 22 | * @private 23 | * @type {number} 24 | */ 25 | _updateSpeed; 26 | 27 | /** 28 | * @private 29 | * @type {{ 30 | * models: T; 31 | * updateSpeed: number; 32 | * }} 33 | */ 34 | _options; 35 | 36 | /** 37 | * @private 38 | * @type {Collection>} 39 | */ 40 | _models = new Collection(); 41 | /** 42 | * @param {{ 43 | * models: T; 44 | * updateSpeed: number; 45 | * }} options 46 | */ 47 | constructor(options) { 48 | if (!options.models) options.models = {}; 49 | this._options = options; 50 | this._updateSpeed = options.updateSpeed; 51 | 52 | for (const key of Object.keys(options.models)) 53 | this._models.set(key, options.models[key].model); 54 | this._init(); 55 | } 56 | 57 | /** 58 | * @private 59 | */ 60 | async _init() { 61 | for (const [modelName, model] of this._models) { 62 | const data = [...(await model.find())]; 63 | for (const doc of data) { 64 | if (modelName === "cooldowns") { 65 | if (!this._cache.get(modelName)) { 66 | this._cache.set( 67 | modelName, 68 | new Collection().set( 69 | doc.type === "local" ? doc.uId : doc.name, 70 | doc.type === "local" 71 | ? new Collection().set(doc.name, doc) 72 | : doc, 73 | ), 74 | ); 75 | } else { 76 | if (doc.type === "local") { 77 | if (!this._cache.get(modelName).get(doc.uId)) 78 | this._cache 79 | .get(modelName) 80 | .set(doc.uId, new Collection().set(doc.name, doc)); 81 | else this._cache.get(modelName).get(doc.uId).set(doc.name, doc); 82 | } else this._cache.get(modelName).set(doc.name, doc); 83 | } 84 | } else { 85 | if (!this._cache.get(modelName)) 86 | this._cache.set( 87 | modelName, 88 | new Collection().set( 89 | doc[this._options.models[modelName].getBy] || doc.name, 90 | doc, 91 | ), 92 | ); 93 | else 94 | this._cache 95 | .get(modelName) 96 | .set(doc[this._options.models[modelName].getBy] || doc.name, doc); 97 | } 98 | } 99 | } 100 | this._startUpdateCycle(); 101 | } 102 | 103 | /** 104 | * @public 105 | * @param {keyof T} type 106 | * @param {string} findBy 107 | * @param {string=} command 108 | */ 109 | getDocument(type, findBy, command) { 110 | if (!this._cache.get(type)) return undefined; 111 | if (type === "cooldown") { 112 | if (!this._cache.get(type).get(findBy)) return undefined; 113 | else return this._cache.get(type).get(findBy).get(command); 114 | } else return this._cache.get(type).get(findBy); 115 | } 116 | 117 | /** 118 | * @public 119 | * @param {keyof T} type 120 | * @param {Document} doc 121 | */ 122 | insertDocument(type, doc) { 123 | if (type === "cooldowns") { 124 | if (!this._cache.get(type)) this._cache.set(type, new Collection()); 125 | 126 | if (doc.type === "local") { 127 | if (!this._cache.get(type).get(doc.uId)) { 128 | this._cache 129 | .get(type) 130 | .set(doc.uId, new Collection().set(doc.name, doc)); 131 | } else this._cache.get(type).get(doc.uId).set(doc.name, doc); 132 | } else this._cache.get(type).set(doc.name, doc); 133 | } else { 134 | if (!this._cache.get(type)) { 135 | this._cache.set( 136 | type, 137 | new Collection().set( 138 | doc[this._options.models[type].getBy] || doc.name, 139 | doc, 140 | ), 141 | ); 142 | } else { 143 | this._cache 144 | .get(type) 145 | .set(doc[this._options.models[type].getBy] || doc.name, doc); 146 | } 147 | } 148 | } 149 | 150 | /** 151 | * @public 152 | * @param {keyof T} type 153 | * @param {Document} update 154 | */ 155 | updateDocument(type, update) { 156 | this._cache 157 | .get(type) 158 | .set(update[this._options.models[type].getBy] || update.name, update); 159 | } 160 | 161 | /** 162 | * @public 163 | * @param {keyof T} type 164 | * @param {string} findBy 165 | * @param {Document} document 166 | */ 167 | async deleteDocument(type, findBy, document) { 168 | if (type === "cooldown") { 169 | if (document.uId) { 170 | this._cache.get(type).get(document.uId).delete(findBy); 171 | await document.delete(); 172 | } else { 173 | this._cache.get(type).delete(findBy); 174 | await document.delete(); 175 | } 176 | } else { 177 | this._cache.get(type).delete(findBy); 178 | await document.delete(); 179 | } 180 | } 181 | 182 | /** @private */ 183 | _startUpdateCycle() { 184 | setInterval(async () => { 185 | for (const [docName, collection] of this._cache) { 186 | if (docName === "cooldowns") { 187 | const model = this._models.get(docName); 188 | for (const [key, doc_or_collection] of collection) { 189 | if (!isNaN(parseInt(key)) && key.length === 18) { 190 | for (const [command, doc] of doc_or_collection) { 191 | const query = {}; 192 | query["uId"] = doc.uId; 193 | query["name"] = command; 194 | query["type"] = "local"; 195 | if (await model.findOne(query)) 196 | await model.findOneAndUpdate(query, doc); 197 | else await model.create(doc); 198 | } 199 | } else { 200 | const query = {}; 201 | query["name"] = key; 202 | query["type"] = "global"; 203 | if (await model.findOne(query)) 204 | await model.findOneAndUpdate(query, doc_or_collection); 205 | else await model.create(doc_or_collection); 206 | } 207 | } 208 | } else { 209 | const model = this._models.get(docName); 210 | for (const [key, document] of collection) { 211 | const query = {}; 212 | query[this._options.models[docName].getBy] = key; 213 | if (await model.findOne(query)) 214 | await model.findOneAndUpdate(query, document); 215 | else await model.create(document); 216 | } 217 | } 218 | } 219 | }, this._updateSpeed); 220 | } 221 | }; 222 | -------------------------------------------------------------------------------- /src/Base/DefaultCommands/Commands.js: -------------------------------------------------------------------------------- 1 | const Command = require("../Command"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const ArgumentValidator = require("../Handling/ArgumentValidator"); 4 | const DisabledCommands = require("../../Database/models/disabled-commands"); 5 | 6 | module.exports = new Command({ 7 | aliases: ["cmd"], 8 | botPermissions: ["SEND_MESSAGES"], 9 | cooldown: 3000, 10 | description: "Enable or disable commands", 11 | details: 12 | "Allows you to enable or disable specific commands in the current server", 13 | devOnly: false, 14 | dmOnly: false, 15 | globalCooldown: 0, 16 | guildOnly: true, 17 | maxArgs: 2, 18 | minArgs: 2, 19 | name: "command", 20 | noDisable: true, 21 | nsfw: false, 22 | testOnly: false, 23 | usage: "{prefix}command ", 24 | userPermissions: ["MANAGE_GUILD"], 25 | category: "configuration", 26 | validator: new ArgumentValidator({ 27 | validate: ({ args, client, message, prefix }) => { 28 | const commands = new Set(client.commands.map((c) => c.name)); 29 | if (args[0] !== "enable" && args[0] !== "disable") 30 | return "INVALID_ARGS_0"; 31 | else if (!commands.has(args[1])) return "INVALID_ARGS_1"; 32 | }, 33 | onError: ({ args, prefix, message, client, error, language }) => { 34 | if (error === "INVALID_ARGS_0") { 35 | const res = client.defaultResponses.getValue( 36 | language, 37 | "COMMANDS_COMMAND", 38 | "INVALID_ARGS_ERROR", 39 | client.defaultResponses.fileData[language].COMMANDS_COMMAND 40 | .INVALID_ARGS_ERROR.embed 41 | ? { 42 | description: [ 43 | { 44 | key: "USAGE", 45 | replace: `\`${prefix}command \``, 46 | }, 47 | ], 48 | } 49 | : [ 50 | { 51 | key: "USAGE", 52 | replace: `\`${prefix}command \``, 53 | }, 54 | ], 55 | ); 56 | 57 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 58 | else message.channel.send(res); 59 | } else if (error === "INVALID_ARGS_1") { 60 | const res = client.defaultResponses.getValue( 61 | language, 62 | "COMMANDS_COMMAND", 63 | "NON_EXISTANT_COMMAND", 64 | client.defaultResponses.fileData[language].COMMANDS_COMMAND 65 | .NON_EXISTANT_COMMAND.embed 66 | ? { 67 | description: [ 68 | { 69 | key: "COMMAND", 70 | replace: args[0], 71 | }, 72 | ], 73 | } 74 | : [ 75 | { 76 | key: "COMMAND", 77 | replace: args[1], 78 | }, 79 | ], 80 | ); 81 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 82 | else message.channel.send(res); 83 | } 84 | }, 85 | }), 86 | run: async ({ message, client, args, language }) => { 87 | let DisabledDoc = client.databaseCache.getDocument( 88 | "disabledcommands", 89 | message.guild.id, 90 | ); 91 | if (!DisabledDoc) 92 | DisabledDoc = new DisabledCommands({ 93 | gId: message.guild.id, 94 | commands: [], 95 | categories: [], 96 | }); 97 | 98 | const enabledDisabled = args[0].toLowerCase(); 99 | const commandName = args[1]; 100 | 101 | if (enabledDisabled === "enable") { 102 | const res = client.defaultResponses.getValue( 103 | language, 104 | "COMMANDS_COMMAND", 105 | "ALREADY_ENABLED", 106 | client.defaultResponses.fileData[language].COMMANDS_COMMAND 107 | .ALREADY_ENABLED.embed 108 | ? { 109 | description: [ 110 | { 111 | key: "COMMAND", 112 | replace: commandName, 113 | }, 114 | ], 115 | } 116 | : [ 117 | { 118 | key: "COMMAND", 119 | replace: commandName, 120 | }, 121 | ], 122 | ); 123 | if (!DisabledDoc.commands.includes(commandName)) { 124 | if (res instanceof MessageEmbed) 125 | return message.channel.send({ embed: res }); 126 | else return message.channel.send(res); 127 | } 128 | 129 | const i = DisabledDoc.commands.findIndex((v) => v === commandName); 130 | DisabledDoc.commands.splice(i, 1); 131 | } else if (enabledDisabled === "disable") { 132 | if (client.commands.get(commandName).noDisable) { 133 | const res = client.defaultResponses.getValue( 134 | language, 135 | "COMMANDS_COMMAND", 136 | "NO_DISABLE", 137 | client.defaultResponses.fileData[language].COMMANDS_COMMAND.NO_DISABLE 138 | .embed 139 | ? { 140 | description: [ 141 | { 142 | key: "COMMAND", 143 | replace: commandName, 144 | }, 145 | ], 146 | } 147 | : [ 148 | { 149 | key: "COMMAND", 150 | replace: commandName, 151 | }, 152 | ], 153 | ); 154 | if (res instanceof MessageEmbed) 155 | return message.channel.send({ embed: res }); 156 | else return message.channel.send(res); 157 | } 158 | 159 | if (DisabledDoc.commands.includes(commandName)) { 160 | const res = client.defaultResponses.getValue( 161 | language, 162 | "COMMANDS_COMMAND", 163 | "ALREADY_DISABLED", 164 | client.defaultResponses.fileData[language].COMMANDS_COMMAND 165 | .ALREADY_DISABLED.embed 166 | ? { 167 | description: [ 168 | { 169 | key: "COMMAND", 170 | replace: commandName, 171 | }, 172 | ], 173 | } 174 | : [ 175 | { 176 | key: "COMMAND", 177 | replace: commandName, 178 | }, 179 | ], 180 | ); 181 | if (res instanceof MessageEmbed) 182 | return message.channel.send({ embed: res }); 183 | else return message.channel.send(res); 184 | } 185 | DisabledDoc.commands.push(commandName); 186 | } 187 | 188 | if (!client.databaseCache.getDocument("disabledcommands", message.guild.id)) 189 | client.databaseCache.insertDocument("disabledcommands", DisabledDoc); 190 | else client.databaseCache.updateDocument("disabledcommands", DisabledDoc); 191 | 192 | const successRes = client.defaultResponses.getValue( 193 | language, 194 | "COMMANDS_COMMAND", 195 | "SUCCESS", 196 | client.defaultResponses.fileData[language].COMMANDS_COMMAND.SUCCESS.embed 197 | ? { 198 | description: [ 199 | { 200 | key: "ACTION", 201 | replace: `${enabledDisabled}d`, 202 | }, 203 | { 204 | key: "COMMAND", 205 | replace: commandName, 206 | }, 207 | ], 208 | } 209 | : [ 210 | { 211 | key: "ACTION", 212 | replace: `${enabledDisabled}d`, 213 | }, 214 | { 215 | key: "COMMAND", 216 | replace: commandName, 217 | }, 218 | ], 219 | ); 220 | 221 | if (successRes instanceof MessageEmbed) 222 | return message.channel.send({ embed: successRes }); 223 | else return message.channel.send(successRes); 224 | }, 225 | }); 226 | -------------------------------------------------------------------------------- /src/Base/DefaultCommands/RequiredRoles.js: -------------------------------------------------------------------------------- 1 | const Command = require("../Command"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const ArgumentValidator = require("../Handling/ArgumentValidator"); 4 | const requiredRoles = require("../../Database/models/required-roles"); 5 | 6 | module.exports = new Command({ 7 | aliases: ["reqroles"], 8 | cooldown: 3000, 9 | description: "Set or remove required roles", 10 | details: 11 | "Allows you to set or remove required roles for a specific command in the current server", 12 | devOnly: false, 13 | dmOnly: false, 14 | globalCooldown: 0, 15 | guildOnly: true, 16 | maxArgs: 3, 17 | minArgs: 3, 18 | name: "requiredroles", 19 | noDisable: true, 20 | nsfw: false, 21 | testOnly: false, 22 | usage: "{prefix}requiredroles ", 23 | userPermissions: ["MANAGE_ROLES"], 24 | botPermissions: ["SEND_MESSAGES"], 25 | category: "configuration", 26 | validator: new ArgumentValidator({ 27 | validate: ({ args, client, message }) => { 28 | const role = 29 | message.mentions.roles.first() || 30 | message.guild.roles.cache.get(args[1]); 31 | if (args[0] !== "add" && args[0] !== "remove") return "INVALID_ARGS_0"; 32 | else if (!role) return "NO_ROLE"; 33 | else if (!client.commands.get(args[2])) return "UNKNOWN_COMMAND"; 34 | }, 35 | onError: ({ args, prefix, message, client, error, language }) => { 36 | if (error === "INVALID_ARGS_0") { 37 | const res = client.defaultResponses.getValue( 38 | language, 39 | "ROLES_COMMAND", 40 | "INVALID_ARGUMENTS", 41 | client.defaultResponses.fileData[language].ROLES_COMMAND 42 | .INVALID_ARGUMENTS.embed 43 | ? { 44 | description: [ 45 | { 46 | key: "USAGE", 47 | replace: `\`${prefix}requiredroles [add/remove] [role] [command]\``, 48 | }, 49 | ], 50 | } 51 | : [ 52 | { 53 | key: "USAGE", 54 | replace: `\`${prefix}requiredroles [add/remove] [role] [command]\``, 55 | }, 56 | ], 57 | ); 58 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 59 | else message.channel.send(res); 60 | } else if (error === "NO_ROLE") { 61 | const res = client.defaultResponses.getValue( 62 | language, 63 | "ROLES_COMMAND", 64 | "INVALID_ROLE", 65 | client.defaultResponses.fileData[language].ROLES_COMMAND.INVALID_ROLE 66 | .embed 67 | ? { 68 | description: [ 69 | { 70 | key: "ACTION", 71 | replace: args[0], 72 | }, 73 | ], 74 | } 75 | : [ 76 | { 77 | key: "ACTION", 78 | replace: args[0], 79 | }, 80 | ], 81 | ); 82 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 83 | else message.channel.send(res); 84 | } else if (error === "UNKNOWN_COMMAND") { 85 | const res = client.defaultResponses.getValue( 86 | language, 87 | "ROLES_COMMAND", 88 | "INVALID_COMMAND", 89 | client.defaultResponses.fileData[language].ROLES_COMMAND 90 | .INVALID_COMMAND.embed 91 | ? { 92 | description: [ 93 | { 94 | key: "COMMAND", 95 | replace: args[2], 96 | }, 97 | ], 98 | } 99 | : [ 100 | { 101 | key: "COMMAND", 102 | replace: args[2], 103 | }, 104 | ], 105 | ); 106 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 107 | else message.channel.send(res); 108 | } 109 | }, 110 | }), 111 | run: async ({ args, client, message, language }) => { 112 | let reqRolesDoc = client.databaseCache.getDocument( 113 | "requriedroles", 114 | message.guild.id, 115 | ); 116 | if (!reqRolesDoc) 117 | reqRolesDoc = new requiredRoles({ 118 | gId: message.guild.id, 119 | }); 120 | 121 | const addRemove = args[0]; 122 | const role = 123 | message.mentions.roles.first() || message.guild.roles.cache.get(args[1]); 124 | const command = args[2]; 125 | 126 | const reqRolesObject = reqRolesDoc.requiredRoles.find( 127 | (ob) => ob.command === command, 128 | ); 129 | if (addRemove === "add") { 130 | if (reqRolesObject) { 131 | if (reqRolesObject.roles.find((s) => s === role.id)) { 132 | const res = client.defaultResponses.getValue( 133 | language, 134 | "ROLES_COMMAND", 135 | "ALREADY_ADDED", 136 | client.defaultResponses.fileData[language].ROLES_COMMAND 137 | .ALREADY_ADDED.embed 138 | ? { 139 | description: [ 140 | { 141 | key: "ROLE", 142 | replace: `**${role.name}**`, 143 | }, 144 | { 145 | key: "COMMAND", 146 | replace: `**${command}**`, 147 | }, 148 | ], 149 | } 150 | : [ 151 | { 152 | key: "ROLE", 153 | replace: `**${role.name}**`, 154 | }, 155 | { 156 | key: "COMMAND", 157 | replace: `**${command}**`, 158 | }, 159 | ], 160 | ); 161 | if (res instanceof MessageEmbed) 162 | return message.channel.send({ embed: res }); 163 | else return message.channel.send(res); 164 | } 165 | reqRolesObject.roles.push(role.id); 166 | } else { 167 | reqRolesDoc.requiredRoles.push({ 168 | command, 169 | roles: [role.id], 170 | }); 171 | } 172 | } else if (addRemove === "remove") { 173 | if (reqRolesObject) { 174 | if (!reqRolesObject.roles.find((s) => s === role.id)) { 175 | const res = client.defaultResponses.getValue( 176 | language, 177 | "ROLES_COMMAND", 178 | "ALREADY_REMOVED", 179 | client.defaultResponses.fileData[language].ROLES_COMMAND 180 | .ALREADY_REMOVED.embed 181 | ? { 182 | description: [ 183 | { 184 | key: "ROLE", 185 | replace: `**${role.name}**`, 186 | }, 187 | { 188 | key: "COMMAND", 189 | replace: `**${command}**`, 190 | }, 191 | ], 192 | } 193 | : [ 194 | { 195 | key: "ROLE", 196 | replace: `**${role.name}**`, 197 | }, 198 | { 199 | key: "COMMAND", 200 | replace: `**${command}**`, 201 | }, 202 | ], 203 | ); 204 | if (res instanceof MessageEmbed) 205 | return message.channel.send({ embed: res }); 206 | else return message.channel.send(res); 207 | } 208 | const i = reqRolesObject.roles.findIndex((s) => s === role.id); 209 | reqRolesObject.roles.splice(i, 1); 210 | } else { 211 | const res = client.defaultResponses.getValue( 212 | language, 213 | "ROLES_COMMAND", 214 | "ALREADY_REMOVED", 215 | client.defaultResponses.fileData[language].ROLES_COMMAND 216 | .ALREADY_REMOVED.embed 217 | ? { 218 | description: [ 219 | { 220 | key: "ROLE", 221 | replace: `**${role.name}**`, 222 | }, 223 | { 224 | key: "COMMAND", 225 | replace: `**${command.name}**`, 226 | }, 227 | ], 228 | } 229 | : [ 230 | { 231 | key: "ROLE", 232 | replace: `**${role.name}**`, 233 | }, 234 | { 235 | key: "COMMAND", 236 | replace: `**${command}**`, 237 | }, 238 | ], 239 | ); 240 | if (res instanceof MessageEmbed) 241 | return message.channel.send({ embed: res }); 242 | else return message.channel.send(res); 243 | } 244 | } 245 | 246 | if (!client.databaseCache.getDocument("requriedroles", message.guild.id)) 247 | client.databaseCache.insertDocument("requriedroles", reqRolesDoc); 248 | else client.databaseCache.updateDocument("requriedroles", reqRolesDoc); 249 | 250 | const successRes = client.defaultResponses.getValue( 251 | language, 252 | "ROLES_COMMAND", 253 | "SUCCESS", 254 | client.defaultResponses.fileData[language].ROLES_COMMAND.SUCCESS.embed 255 | ? { 256 | description: [ 257 | { 258 | key: "ACTION", 259 | replace: `${addRemove === "add" ? "added" : "removed"}`, 260 | }, 261 | { 262 | key: "ROLE", 263 | replace: `**${role.name}**`, 264 | }, 265 | { 266 | key: "COMMAND", 267 | replace: command, 268 | }, 269 | ], 270 | } 271 | : [ 272 | { 273 | key: "ACTION", 274 | replace: `${addRemove === "add" ? "added" : "removed"}`, 275 | }, 276 | { 277 | key: "ROLE", 278 | replace: `**${role.name}**`, 279 | }, 280 | { 281 | key: "COMMAND", 282 | replace: command, 283 | }, 284 | ], 285 | ); 286 | if (successRes instanceof MessageEmbed) 287 | return message.channel.send({ embed: successRes }); 288 | else return message.channel.send({ embed: successRes }); 289 | }, 290 | }); 291 | -------------------------------------------------------------------------------- /src/Base/DefaultCommands/Help.js: -------------------------------------------------------------------------------- 1 | const Command = require("../Command"); 2 | const ArgumentValidator = require("../Handling/ArgumentValidator"); 3 | const { MessageEmbed, MessageReaction, User } = require("discord.js"); 4 | const { ProperCase, FormatCooldown, FormatPerms } = require("../../Functions"); 5 | 6 | module.exports = new Command({ 7 | aliases: ["commands"], 8 | cooldown: 3000, 9 | description: "Help Command", 10 | details: "A help command to recieve help", 11 | devOnly: false, 12 | dmOnly: false, 13 | globalCooldown: 0, 14 | guildOnly: false, 15 | maxArgs: 1, 16 | minArgs: 0, 17 | name: "help", 18 | noDisable: true, 19 | nsfw: false, 20 | testOnly: false, 21 | usage: "{prefix}help [command]", 22 | userPermissions: ["SEND_MESSAGES"], 23 | botPermissions: ["EMBED_LINKS"], 24 | category: "help", 25 | validator: new ArgumentValidator({ 26 | validate: ({ client, args }) => { 27 | const command_category = (args[0] || "").toLowerCase(); 28 | const command = client.commands.get( 29 | [...client.commands.keys()].filter(command => command.toLowerCase() === command_category)[0] || [...client.aliases.keys()].filter(alias => alias.toLowerCase() === command_category)[0] 30 | ); 31 | const category = client.commands.filter( 32 | (c) => c.category.toLowerCase() === command_category, 33 | ); 34 | 35 | if (!command && category.size < 1 && command_category) 36 | return "NON_EXISTANT_COMMAND_CATEGORY"; 37 | }, 38 | onError: ({ args, prefix, message, client, error, language }) => { 39 | if (error === "NON_EXISTANT_COMMAND_CATEGORY") { 40 | const command_category = args[0] || "None"; 41 | const res = client.defaultResponses.getValue( 42 | language, 43 | "HELP_COMMAND", 44 | "INVALID_COMMAND_CATEGORY", 45 | client.defaultResponses.fileData[language].HELP_COMMAND 46 | .INVALID_COMMAND_CATEGORY.embed 47 | ? { 48 | description: [ 49 | { 50 | key: "COMMAND_CATEGORY", 51 | replace: ProperCase(command_category), 52 | }, 53 | { 54 | key: "PREFIX", 55 | replace: prefix, 56 | }, 57 | ], 58 | } 59 | : [ 60 | { 61 | key: "COMMAND_CATEGORY", 62 | replace: ProperCase(command_category), 63 | }, 64 | { 65 | key: "PREFIX", 66 | replace: prefix, 67 | }, 68 | ], 69 | ); 70 | if (res instanceof MessageEmbed) message.channel.send({ embed: res }); 71 | else message.channel.send(res); 72 | } 73 | }, 74 | }), 75 | run: async ({ args, prefix, message, client, language }) => { 76 | const command_category = (args[0] || "").toLowerCase(); 77 | const command = client.commands.get( 78 | [...client.commands.keys()].filter(command => command.toLowerCase() === command_category)[0] || [...client.aliases.keys()].filter(alias => alias.toLowerCase() === command_category)[0] 79 | ); 80 | const category = client.commands.filter( 81 | (c) => c.category.toLowerCase() === command_category, 82 | ); 83 | 84 | const helpEmbed = new MessageEmbed() 85 | .setColor("00DCFF") 86 | .setAuthor( 87 | " [Optional]", 88 | client.user.displayAvatarURL({ format: "png" }), 89 | ) 90 | .setFooter( 91 | `Requested by ${message.author.username}`, 92 | message.author.displayAvatarURL({ format: "png", dynamic: true }), 93 | ) 94 | .setTimestamp(); 95 | 96 | if (command) { 97 | helpEmbed 98 | .setTitle(`${ProperCase(command.name)} Help Menu`) 99 | .setDescription( 100 | `*${ 101 | command.details || "No extra details provided!" 102 | }*\n\n**Usage:** ${command.usage.replace( 103 | /{prefix}/gi, 104 | prefix, 105 | )}\n**Required Member Permissions:** ${ 106 | FormatPerms(command.userPermissions) || "None" 107 | }\n**Required Bot Permissions:** ${ 108 | FormatPerms(command.botPermissions) || "None" 109 | }\n**Cooldown:** ${ 110 | FormatCooldown(command.cooldown) || "None" 111 | }\n**Global Cooldown** ${ 112 | FormatCooldown(command.globalCooldown) || "None" 113 | }`, 114 | ); 115 | return message.channel.send("", { embed: helpEmbed }); 116 | } else if (category.size) { 117 | helpEmbed.setTitle(`${ProperCase(command_category)} Help Menu`); 118 | 119 | const cateCommands = category.array(); 120 | 121 | /** @type {Command[][]} */ 122 | const pages = []; 123 | for (let i = 0; i < cateCommands.length; i += 5) 124 | pages.push(cateCommands.slice(i, i + 5)); 125 | 126 | let curPage = 0; 127 | const page1 = pages[curPage]; 128 | pages.length > 1 129 | ? helpEmbed.setAuthor( 130 | `Page: 1/${pages.length}`, 131 | client.user.displayAvatarURL({ format: "png" }), 132 | ) 133 | : null; 134 | helpEmbed 135 | .setDescription( 136 | page1 137 | .map( 138 | (c) => 139 | `**${c.name}** → ${ 140 | c.description 141 | }\n**Aliases:** ${c.aliases.join( 142 | ", ", 143 | )}\n**Usage:** ${c.usage.replace(/{prefix}/gi, prefix)}`, 144 | ) 145 | .join("\n\n"), 146 | ) 147 | .setFooter( 148 | `Use ${prefix}help [command] for more info`, 149 | message.author.displayAvatarURL({ format: "png" }), 150 | ); 151 | 152 | const helpMessage = await message.channel.send("", { embed: helpEmbed }); 153 | 154 | if (pages.length > 1) { 155 | const emojis = ["⬅️", "❌", "➡️"]; 156 | emojis.forEach((e) => helpMessage.react(e)); 157 | 158 | /** 159 | * @param {MessageReaction} reaction 160 | * @param {User} user 161 | */ 162 | const filter = (reaction, user) => 163 | message.author.id === user.id && emojis.includes(reaction.emoji.name); 164 | 165 | const collector = helpMessage.createReactionCollector(filter, { 166 | time: 90 * 1000, 167 | }); 168 | 169 | collector.on("collect", async (reaction, user) => { 170 | switch (reaction.emoji.name) { 171 | case "⬅️": 172 | if (curPage > 0) curPage--; 173 | break; 174 | case "➡️": 175 | if (curPage < pages.length - 1) curPage++; 176 | break; 177 | case "❌": 178 | await helpMessage.reactions.removeAll(); 179 | return collector.stop(); 180 | } 181 | 182 | helpEmbed 183 | .setAuthor( 184 | `Page: ${curPage + 1}/${pages.length}`, 185 | client.user.displayAvatarURL({ format: "png" }), 186 | ) 187 | .setDescription( 188 | pages[curPage] 189 | .map( 190 | (c) => 191 | `**${c.name}** → ${ 192 | c.description 193 | }\n**Aliases:** ${c.aliases.join( 194 | ", ", 195 | )}\n**Usage:** ${c.usage.replace(/{prefix}/gi, prefix)}`, 196 | ) 197 | .join("\n\n"), 198 | ); 199 | await reaction.users 200 | .remove(user) 201 | .catch((err) => client.logError({ data: err })); 202 | await helpMessage.edit(helpEmbed); 203 | }); 204 | } 205 | } else { 206 | helpEmbed 207 | .setTitle("Help Menu") 208 | .setAuthor(`Total Commands: ${client.commands.size}`) 209 | .setFooter(`Use ${prefix}help [category] for more info`); 210 | 211 | const categories = Array.from( 212 | new Set(client.commands.map((c) => c.category)), 213 | ); 214 | /** @type {string[][]} */ 215 | const pages = []; 216 | 217 | for (let i = 0; i < categories.length; i += 6) 218 | pages.push(categories.slice(i, i + 6)); 219 | 220 | pages.length > 1 221 | ? helpEmbed.setAuthor( 222 | `Page: 1/${pages.length}`, 223 | client.user.displayAvatarURL({ format: "png" }), 224 | ) 225 | : null; 226 | let curPage = 0; 227 | for (const category of pages[curPage]) 228 | helpEmbed.addField( 229 | `${category} [${ 230 | client.commands.filter((c) => c.category === category).size 231 | }]`, 232 | client.commands 233 | .filter((c) => c.category === category) 234 | .array() 235 | .slice(0, 3) 236 | .map((c) => `\`${c.name}\``) 237 | .join(", ") + "...", 238 | ); 239 | 240 | const helpMessage = await message.channel.send("", { embed: helpEmbed }); 241 | 242 | if (pages.length > 1) { 243 | const emojis = ["⬅️", "❌", "➡️"]; 244 | emojis.forEach((e) => helpMessage.react(e)); 245 | 246 | /** 247 | * @param {MessageReaction} reaction 248 | * @param {User} user 249 | */ 250 | const filter = (reaction, user) => 251 | message.author.id === user.id && emojis.includes(reaction.emoji.name); 252 | 253 | const collector = helpMessage.createReactionCollector(filter, { 254 | time: 90 * 1000, 255 | }); 256 | 257 | collector.on("collect", async (reaction, user) => { 258 | const reactedEmbed = new MessageEmbed() 259 | .setColor("BLUE") 260 | .setAuthor( 261 | `Page: ${curPage + 1}/${pages.length}`, 262 | client.user.displayAvatarURL({ format: "png" }), 263 | ) 264 | .setTimestamp() 265 | .setTitle("Help Menu") 266 | .setFooter(`Use ${prefix}help [category] for more info`); 267 | 268 | switch (reaction.emoji.name) { 269 | case "⬅️": 270 | if (curPage > 0) curPage--; 271 | break; 272 | case "➡️": 273 | if (curPage < pages.length - 1) curPage++; 274 | break; 275 | case "❌": 276 | await helpMessage.reactions.removeAll(); 277 | return collector.stop(); 278 | } 279 | 280 | for (const category of pages[curPage]) 281 | reactedEmbed.addField( 282 | `${category} [${ 283 | client.commands.filter((c) => c.category === category).size 284 | }]`, 285 | client.commands 286 | .filter((c) => c.category === category) 287 | .array() 288 | .slice(0, 3) 289 | .map((c) => `\`${c.name}\``) 290 | .join(", ") + "...", 291 | ); 292 | 293 | await reaction.users 294 | .remove(user) 295 | .catch((err) => client.logError({ data: err })); 296 | await helpMessage.edit(reactedEmbed); 297 | }); 298 | } 299 | } 300 | }, 301 | }); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { Client, Collection, MessageEmbed } = require("discord.js"); 2 | const colors = require("colors"); 3 | const { mkdirSync, writeFileSync } = require("fs"); 4 | const { CDClient } = require("./Base/CDClient"); 5 | const { 6 | categories, 7 | requiredroles, 8 | commands, 9 | help, 10 | setprefix, 11 | language: lang, 12 | } = require("./Base/DefaultCommands"); 13 | const Commands = require("./registry/Commands"); 14 | const Events = require("./registry/Events"); 15 | const { 16 | database, 17 | cooldown, 18 | disabledCommands, 19 | guildLanguage, 20 | prefixes, 21 | requiredRoles, 22 | userLanguage, 23 | } = require("./Database/models"); 24 | 25 | const Cooldowns = require("./Base/Handling/CooldownHandler"); 26 | const MessageJSON = require("./Base/Handling/MessageJSON"); 27 | const FeatureHandler = require("./Base/Handling/FeatureHandler"); 28 | const Cache = require("./Base/Handling/CacheHandler"); 29 | 30 | class CDCommands { 31 | /** 32 | * @private 33 | * @type {CDClient} 34 | */ 35 | _client; 36 | /** 37 | * @private 38 | * @type {string} 39 | */ 40 | _commandsDir; 41 | /** 42 | * @private 43 | * @type {string} 44 | */ 45 | _eventsDir; 46 | /** 47 | * @private 48 | * @type {string} 49 | */ 50 | _featuresDir; 51 | /** 52 | * @private 53 | * @type {string[]} 54 | */ 55 | _testServers; 56 | /** 57 | * @private 58 | * @type {string[]} 59 | */ 60 | _devs; 61 | /** 62 | * @private 63 | * @type {string} 64 | */ 65 | _defaultPrefix; 66 | /** 67 | * @private 68 | * @type {string} 69 | */ 70 | _mongoURI; 71 | /** 72 | * @private 73 | * @type {boolean} 74 | */ 75 | _ignoreBots; 76 | /** 77 | * @private 78 | * @type {boolean} 79 | */ 80 | _customMessageEvent; 81 | /** 82 | * @private 83 | * @type {(keyof {"help", "command", "category", "language", "requiredroles", "setprefix"})[]} 84 | */ 85 | _disabledDefaultCommands; 86 | /** @private */ 87 | _cacheUpdateSpeed = 90 * 1000; 88 | 89 | /** 90 | * @param {Client} client 91 | * @param {{ 92 | * commandsDir?: string; 93 | * eventsDir?: string; 94 | * featuresDir?: string; 95 | * testServers?: string[]; 96 | * customMessageEvent?: boolean; 97 | * disabledDefaultCommands?: (keyof {"help", "command", "category", "language", "requiredroles", "setprefix"})[]; 98 | * devs?: string[]; 99 | * defaultPrefix: string; 100 | * mongoURI: string; 101 | * ignoreBots?: boolean; 102 | * cacheUpdateSpeed?: number; 103 | * MessageJSONPath?: string; 104 | * }} options 105 | */ 106 | constructor(client, options) { 107 | try { 108 | mkdirSync("./.vscode"); 109 | writeFileSync( 110 | "./.vscode/settings.json", 111 | JSON.stringify( 112 | { 113 | "json.schemas": [ 114 | { 115 | fileMatch: ["message.json", "messages.json"], 116 | url: 117 | "./node_modules/cdcommands/src/Base/json-schema/message.json", 118 | }, 119 | ], 120 | }, 121 | null, 122 | 2, 123 | ), 124 | ); 125 | console.log( 126 | "[Success] ".green + 127 | ".vscode/settings.json has been initialized, you can now use intellisense with your" + 128 | " message.json ".green + 129 | "file!", 130 | ); 131 | } catch (err) {} 132 | if (!options.commandsDir) options.commandsDir = "commands"; 133 | if (!options.eventsDir) options.eventsDir = "events"; 134 | if (!options.featuresDir) options.featuresDir = "features"; 135 | if (!options.testServers) options.testServers = []; 136 | if (!options.devs) options.devs = []; 137 | if (!options.disabledDefaultCommands) options.disabledDefaultCommands = []; 138 | if (!options.MessageJSONPath) options.MessageJSONPath = ""; 139 | if (options.ignoreBots === undefined) options.ignoreBots = true; 140 | 141 | this._client = client; 142 | this._commandsDir = options.commandsDir; 143 | this._eventsDir = options.eventsDir; 144 | this._featuresDir = options.featuresDir; 145 | this._testServers = options.testServers; 146 | this._defaultPrefix = options.defaultPrefix; 147 | this._mongoURI = options.mongoURI; 148 | this._ignoreBots = options.ignoreBots; 149 | this._customMessageEvent = options.customMessageEvent; 150 | this._disabledDefaultCommands = options.disabledDefaultCommands; 151 | this._devs = options.devs; 152 | if (options.cacheUpdateSpeed && options.cacheUpdateSpeed > 0) 153 | this._cacheUpdateSpeed = options.cacheUpdateSpeed; 154 | 155 | this._client.commands = new Collection(); 156 | this._client.aliases = new Collection(); 157 | this._client.defaultPrefix = options.defaultPrefix; 158 | this._client.developers = this._devs; 159 | this._client.testservers = this._testServers; 160 | this._client.ignoreBots = this._ignoreBots; 161 | this._client.defaultResponses = new MessageJSON(options.MessageJSONPath); 162 | 163 | this._client.success = ({ msg, data }) => { 164 | const embed = new MessageEmbed() 165 | .setColor("#2FDD2C") 166 | .setDescription(`${data}`) 167 | .setFooter(`Order request by ${msg.author.tag}`); 168 | return embed; 169 | }; 170 | 171 | this._client.error = ({ msg, data }) => { 172 | const embed = new MessageEmbed() 173 | .setColor("#C93131") 174 | .setDescription(`${data}`); 175 | return embed; 176 | }; 177 | 178 | this._client.load = ({ msg, data }) => { 179 | const embed = new MessageEmbed() 180 | .setColor("#00DCFF") 181 | .setDescription(`${data}`); 182 | return embed; 183 | }; 184 | 185 | this._client.info = ({ msg, data }) => { 186 | const embed = new MessageEmbed() 187 | .setColor("#00DCFF") 188 | .setDescription(`${data}`); 189 | return embed; 190 | }; 191 | 192 | this._client.logReady = ({ data }) => 193 | console.log( 194 | `${colors.brightGreen("[READY]")}`.white + colors.white(` ${data}`), 195 | ); 196 | this._client.logError = ({ data }) => 197 | console.log( 198 | `${colors.brightRed("[ERROR]")}`.white + colors.white(` ${data}`), 199 | ); 200 | this._client.logWarn = ({ data }) => 201 | console.log( 202 | `${colors.yellow("[WARN]")}`.white + colors.white(` ${data}`), 203 | ); 204 | this._client.logInfo = ({ data }) => 205 | console.log( 206 | `${colors.brightCyan("[INFO]")}`.white + colors.white(` ${data}`), 207 | ); 208 | this._client.logDatabase = ({ data }) => 209 | console.log( 210 | `${colors.brightGreen("[DATABASE]")}`.white + colors.white(` ${data}`), 211 | ); 212 | 213 | this._init(); 214 | } 215 | 216 | /** @private */ 217 | async _init() { 218 | if (this._mongoURI) await database(this._mongoURI); 219 | else 220 | this._client.logError({ 221 | data: 222 | "Using mongoose with CDCommands is required, as some features will not function properly.", 223 | }); 224 | 225 | this._client.databaseCache = new Cache({ 226 | models: { 227 | cooldowns: { 228 | model: cooldown, 229 | getBy: "uId", 230 | }, 231 | disabledcommands: { 232 | model: disabledCommands, 233 | getBy: "gId", 234 | }, 235 | prefixes: { 236 | model: prefixes, 237 | getBy: "gId", 238 | }, 239 | requriedroles: { 240 | model: requiredRoles, 241 | getBy: "gId", 242 | }, 243 | guildLanguage: { 244 | model: guildLanguage, 245 | getBy: "gId", 246 | }, 247 | userLanguage: { 248 | model: userLanguage, 249 | getBy: "uId", 250 | }, 251 | }, 252 | updateSpeed: this._cacheUpdateSpeed, 253 | }); 254 | 255 | // const t = [...(await cooldown.find())]; 256 | // console.log(t); 257 | 258 | this._client.cooldowns = new Cooldowns( 259 | [...(await cooldown.find())], 260 | this._client, 261 | ); 262 | 263 | this._client.getLanguage = ({ authorId, guildId }) => { 264 | if (!authorId || typeof authorId !== "string") 265 | this._client.logError({ 266 | data: 267 | 'An invalid "authorId" was provided for fn "getLanguage", unable to get author language, using "guildId" instead', 268 | }); 269 | if ( 270 | (!guildId || typeof guildId !== "string") && 271 | (!authorId || typeof authorId !== "string") 272 | ) 273 | this._client.logError({ 274 | data: 275 | 'An invalid "guildId" was provided for fn "getLanguage", unable to get guildLanguage, defaulting to "en" instead', 276 | }); 277 | const uDBLang = this._client.databaseCache.getDocument( 278 | "userLanguage", 279 | authorId, 280 | ); 281 | const gDBLang = this._client.databaseCache.getDocument( 282 | "guildLanguage", 283 | guildId, 284 | ); 285 | 286 | return uDBLang ? uDBLang.language : gDBLang ? gDBLang.language : "en"; 287 | }; 288 | 289 | this._commands(); 290 | this._events(); 291 | new FeatureHandler(this._client, this._featuresDir); 292 | } 293 | 294 | /** @private */ 295 | async _commands() { 296 | await Commands( 297 | this._commandsDir, 298 | this._client, 299 | this._disabledDefaultCommands.includes("help"), 300 | ); 301 | 302 | const customCommands = [ 303 | setprefix, 304 | requiredroles, 305 | categories, 306 | commands, 307 | help, 308 | lang, 309 | ]; 310 | 311 | for (const command of customCommands) { 312 | if ( 313 | !this._disabledDefaultCommands.includes(command.name) && 314 | !this._client.commands.get(command.name) 315 | ) { 316 | this._client.commands.set(command.name, command); 317 | for (const alias of command.aliases) 318 | this._client.aliases.set(alias, command.name); 319 | } 320 | } 321 | 322 | this._client.logInfo({ 323 | data: `CDCommands >> Loaded ${this._client.commands.size} commands`, 324 | }); 325 | } 326 | 327 | /** @private */ 328 | async _events() { 329 | let totalEvents = await Events( 330 | this._eventsDir, 331 | this._client, 332 | this._customMessageEvent, 333 | ); 334 | 335 | // Default message event 336 | const MsgEvent = require("./Base/Message"); 337 | if (!this._customMessageEvent) { 338 | this._client.on(MsgEvent.name, MsgEvent.run.bind(null, this._client)); 339 | totalEvents++; 340 | } 341 | 342 | this._client.logInfo({ 343 | data: `CDCommands >> Loaded ${totalEvents} events`, 344 | }); 345 | } 346 | 347 | /** @public */ 348 | get defaultPrefix() { 349 | return this._defaultPrefix; 350 | } 351 | 352 | /** @public */ 353 | get testServers() { 354 | return this._testServers; 355 | } 356 | } 357 | 358 | module.exports.CDCommands = CDCommands; 359 | module.exports.Event = require("./Base/Event"); 360 | module.exports.Command = require("./Base/Command"); 361 | module.exports.Validator = require("./Base/Handling/ArgumentValidator"); 362 | module.exports.Feature = require("./Base/Feature"); 363 | module.exports.Models = { 364 | cooldown: require("./Database/models/cooldown"), 365 | disabledCommands: require("./Database/models/disabled-commands"), 366 | prefixes: require("./Database/models/prefixes"), 367 | requiredRoles: require("./Database/models/required-roles"), 368 | guildLanguage: require("./Database/models/guildLanguage"), 369 | userLanguage: require("./Database/models/userLanguage"), 370 | }; 371 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 CreativeDevelopments 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/Base/Handling/MessageJSON.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path"); 2 | const langs = Object.keys(require("./Languages.json")); 3 | const { MessageEmbed } = require("discord.js"); 4 | const { clone } = require("ramda"); 5 | 6 | /** 7 | * @template {import("../message.json")} T 8 | */ 9 | class MessageJSON { 10 | /** 11 | * @private 12 | * @type {string} 13 | */ 14 | _path = "../message.json"; 15 | 16 | /** 17 | * @private 18 | * @type {T} 19 | */ 20 | _fileData; 21 | 22 | /** 23 | * @param {string=} messagePath 24 | */ 25 | constructor(messagePath) { 26 | if (messagePath && messagePath !== "") 27 | this._path = join(require.main.path, messagePath); 28 | try { 29 | require(this._path); 30 | } catch (err) { 31 | throw new ReferenceError('An invalid "message.json" path was provided.'); 32 | } 33 | 34 | this._fileData = require(this._path); 35 | } 36 | /** 37 | * @template {keyof (T["en"])} V 38 | * @template {keyof T["en"][V] extends "embed" ? "" : T["en"][V] extends string ? "" : keyof T["en"][V]} S 39 | * @param {keyof import("./Languages.json")} language 40 | * @param {V} key 41 | * @param {S} secondary_key 42 | * @param {{ 43 | * title?: [{ 44 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 45 | * replace: string 46 | * }]; 47 | * url?: [{ 48 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 49 | * replace: string 50 | * }]; 51 | * author_name?: [{ 52 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 53 | * replace: string 54 | * }] 55 | * author_iconURL?: [{ 56 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 57 | * replace: string 58 | * }] 59 | * color?: [{ 60 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 61 | * replace: string 62 | * }] 63 | * fields?: [ 64 | * { 65 | * name?: [{ 66 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 67 | * replace: string 68 | * }] 69 | * value?: [{ 70 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 71 | * replace: string 72 | * }] 73 | * inline?: [{ 74 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 75 | * replace: string 76 | * }] 77 | * } 78 | * ] 79 | * footer_text?: [{ 80 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 81 | * replace: string 82 | * }] 83 | * footer_iconURL?: [{ 84 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 85 | * replace: string 86 | * }] 87 | * timestamp?: [{ 88 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 89 | * replace: string 90 | * }] 91 | * thumbnail_url?: [{ 92 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 93 | * replace: string 94 | * }] 95 | * description?: [{ 96 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 97 | * replace: string 98 | * }] 99 | * image_url?: [{ 100 | * key: keyof import("../json-schema/replacers.json")["EMBED"] | keyof import("../json-schema/replacers.json") 101 | * replace: string 102 | * }]} | [{ key: keyof import("../json-schema/replacers.json"), replace: string }]} args 103 | * @returns {MessageEmbed | string} 104 | */ 105 | getValue(language, key, secondary_key, args) { 106 | const lang_data = clone(this._fileData[language]); 107 | if (lang_data === undefined || !langs.includes(language)) 108 | return console.log( 109 | "[ERROR] ".red + 110 | `Language "${language}" is unknown. Please provide a valid language.`, 111 | ); 112 | let get = lang_data[key]; 113 | if (get === undefined) 114 | return console.log( 115 | "[ERROR] ".red + 116 | `Primary key "${key}" is unknown. Please provide a valid primary key.`, 117 | ); 118 | 119 | if (typeof get === "object" && !Object.keys(get).includes("embed")) 120 | get = get[secondary_key]; 121 | if (get === undefined) 122 | return console.log( 123 | "[ERROR] ".red + 124 | `Secondary key "${secondary_key}" is unknown. Please provide a valid secondary key.`, 125 | ); 126 | try { 127 | if (!Object.keys(get).includes("embed")) { 128 | if (!(args instanceof Array)) 129 | return console.log( 130 | "[ERROR] ".red + 131 | 'Result is of type "string" but "args" is not an instance of an Array. Use an Array instead.', 132 | ); 133 | 134 | for (const replacer of args) { 135 | const regex = new RegExp(`{${replacer.key}}`, "g"); 136 | get = get.replace(regex, replacer.replace); 137 | } 138 | 139 | return get; 140 | } else { 141 | if (args instanceof Array) 142 | return console.log( 143 | "[ERROR] ".red + 144 | 'Result is an "embed" but "args" is an instance of an Array. Use an Object instead.', 145 | ); 146 | 147 | for (const args_key of Object.keys(args)) { 148 | if (args_key === "fields") { 149 | const fields_keys = args[args_key]; 150 | for (let i = 0; i < fields_keys.length; i++) { 151 | const args_keys_inline_etc = fields_keys[i]; 152 | const get_field = get["embed"][args_key][i]; 153 | 154 | const { inline, value, name } = args_keys_inline_etc; 155 | 156 | if (inline instanceof Array) { 157 | for (let i = 0; i < inline.length; i++) { 158 | const key_value = inline[i]; 159 | if (!key_value.key) { 160 | console.log( 161 | "[WARN] ".yellow + 162 | `Argument value at position "${i}" in "${args_key}->inline" is missing a valid key value, skipping replacer.`, 163 | ); 164 | continue; 165 | } 166 | if (!key_value.replace) { 167 | console.log( 168 | "[WARN] ".yellow + 169 | `Argument value at position "${i}" in "${args_key}->inline" is missing a valid replace value, skipping replacer.`, 170 | ); 171 | continue; 172 | } 173 | const inlineRegex = new RegExp(`{${key_value.key}}`, "g"); 174 | get_field["inline"] = get_field["inline"].replace( 175 | inlineRegex, 176 | key_value.replace, 177 | ); 178 | } 179 | } else if (!(inline instanceof Array) && inline !== undefined) 180 | console.log( 181 | "[WARN] ".yellow + 182 | `Got "inline" value in "fields" at position "${i}" as non-Array.`, 183 | ); 184 | 185 | if (value instanceof Array) { 186 | for (let i = 0; i < value.length; i++) { 187 | const key_value = value[i]; 188 | if (!key_value.key) { 189 | console.log( 190 | "[WARN] ".yellow + 191 | `Argument value at position "${i}" in "${args_key}->value" is missing a valid key value, skipping replacer.`, 192 | ); 193 | continue; 194 | } 195 | if (!key_value.replace) { 196 | console.log( 197 | "[WARN] ".yellow + 198 | `Argument value at position "${i}" in "${args_key}->value" is missing a valid replace value, skipping replacer.`, 199 | ); 200 | continue; 201 | } 202 | const valueRegex = new RegExp(`{${key_value.key}}`, "g"); 203 | get_field["value"] = get_field["value"].replace( 204 | valueRegex, 205 | key_value.replace, 206 | ); 207 | } 208 | } else if (!(value instanceof Array) && value !== undefined) 209 | console.log( 210 | "[WARN] ".yellow + 211 | `Got "value" value in "fields" at position "${i}" as non-Array.`, 212 | ); 213 | if (name instanceof Array) { 214 | for (let i = 0; i < name.length; i++) { 215 | const key_value = name[i]; 216 | if (!key_value.key) { 217 | console.log( 218 | "[WARN] ".yellow + 219 | `Argument value at position "${i}" in "${args_key}->name" is missing a valid key value, skipping replacer.`, 220 | ); 221 | continue; 222 | } 223 | if (!key_value.replace) { 224 | console.log( 225 | "[WARN] ".yellow + 226 | `Argument value at position "${i}" in "${args_key}->name" is missing a valid replace value, skipping replacer.`, 227 | ); 228 | continue; 229 | } 230 | const nameRegex = new RegExp(`{${key_value.key}}`, "g"); 231 | get_field["name"] = get_field["name"].replace( 232 | nameRegex, 233 | key_value.replace, 234 | ); 235 | } 236 | } else if (!(name instanceof Array) && name !== undefined) 237 | console.log( 238 | "[WARN] ".yellow + 239 | `Got "name" value in "fields" at position "${i}" as non-Array.`, 240 | ); 241 | } 242 | continue; 243 | } 244 | 245 | for (let i = 0; i < args[args_key].length; i++) { 246 | const key_value = args[args_key][i]; 247 | if (!key_value.key) { 248 | console.log( 249 | "[WARN] ".yellow + 250 | `Argument value at position "${i}" in "${args_key}" is missing a valid key value, skipping replacer.`, 251 | ); 252 | continue; 253 | } else if (!key_value.replace) { 254 | console.log( 255 | "[WARN] ".yellow + 256 | `Argument value at position "${i}" in "${args_key}" is missing a valid replace value, skipping replacer.`, 257 | ); 258 | continue; 259 | } 260 | if (args_key.includes("_")) { 261 | const _keys = args_key.split("_"); 262 | const regex = new RegExp(`{${key_value.key}}`, "g"); 263 | 264 | get["embed"][_keys[0]][_keys[1]] = get["embed"][_keys[0]][ 265 | _keys[1] 266 | ].replace(regex, key_value.replace); 267 | continue; 268 | } else { 269 | const regex = new RegExp(`{${key_value.key}}`, "g"); 270 | get["embed"][args_key] = get["embed"][args_key].replace( 271 | regex, 272 | key_value.replace, 273 | ); 274 | } 275 | } 276 | } 277 | return new MessageEmbed(get.embed); 278 | } 279 | } catch (err) { 280 | return undefined; 281 | } 282 | } 283 | 284 | get fileData() { 285 | return this._fileData; 286 | } 287 | } 288 | 289 | // new MessageJSON("").getValue("en", "GUILD_ONLY", "", { 290 | 291 | // }) 292 | 293 | module.exports = MessageJSON; 294 | -------------------------------------------------------------------------------- /src/Base/Message.js: -------------------------------------------------------------------------------- 1 | const Event = require("./Event"); 2 | const { 3 | ValidatePermissions, 4 | ProperCase, 5 | ValidateRoles, 6 | FormatCooldown, 7 | } = require("../Functions"); 8 | const { MessageEmbed } = require("discord.js"); 9 | 10 | module.exports = new Event("message", async (client, message) => { 11 | const prefix = message.guild 12 | ? client.databaseCache.getDocument("prefixes", message.guild.id) 13 | ? client.databaseCache.getDocument("prefixes", message.guild.id).prefix 14 | : client.defaultPrefix 15 | : client.defaultPrefix; 16 | 17 | const args = message.content.trim().slice(prefix.length).split(/ +/g); 18 | const commandName = args.shift().toLowerCase(); 19 | 20 | if (!message.content.startsWith(prefix)) return; 21 | if (client.ignoreBots && message.author.bot) return; 22 | 23 | const command = 24 | client.commands.get(commandName) || 25 | client.commands.get(client.aliases.get(commandName)); 26 | if (command) { 27 | const language = client.getLanguage({ 28 | guildId: message.guild ? message.guild.id : "", 29 | authorId: message.author.id, 30 | }); 31 | 32 | // Guild Only 33 | if (command.guildOnly && !message.guild) { 34 | const res = client.defaultResponses.getValue( 35 | language, 36 | "GUILD_ONLY", 37 | "", 38 | client.defaultResponses.fileData[language].GUILD_ONLY.embed 39 | ? { 40 | description: [ 41 | { 42 | key: "COMMAND", 43 | replace: ProperCase(command.name), 44 | }, 45 | ], 46 | } 47 | : [ 48 | { 49 | key: "COMMAND", 50 | replace: ProperCase(command.name), 51 | }, 52 | ], 53 | ); 54 | if (res instanceof MessageEmbed) 55 | return message.channel.send({ embed: res }); 56 | else return message.channel.send(res); 57 | } 58 | // DM only 59 | if (command.dmOnly && message.guild) { 60 | const res = client.defaultResponses.getValue( 61 | language, 62 | "DM_ONLY", 63 | "", 64 | client.defaultResponses.fileData[language].DM_ONLY.embed 65 | ? { 66 | description: [ 67 | { 68 | key: "COMMAND", 69 | replace: ProperCase(command.name), 70 | }, 71 | ], 72 | } 73 | : [ 74 | { 75 | key: "COMMAND", 76 | replace: ProperCase(command.name), 77 | }, 78 | ], 79 | ); 80 | if (res instanceof MessageEmbed) 81 | return message.channel.send({ embed: res }); 82 | else return message.channel.send(res); 83 | } 84 | // NSFW Channel 85 | if (!message.channel.nsfw && command.nsfw) { 86 | const res = client.defaultResponses.getValue( 87 | language, 88 | "NSFW_ONLY", 89 | "", 90 | client.defaultResponses.fileData[language].NSFW_ONLY.embed 91 | ? { 92 | description: [ 93 | { 94 | key: "COMMAND", 95 | replace: ProperCase(command.name), 96 | }, 97 | ], 98 | } 99 | : [ 100 | { 101 | key: "COMMAND", 102 | replace: ProperCase(command.name), 103 | }, 104 | ], 105 | ); 106 | if (res instanceof MessageEmbed) 107 | return message.channel.send({ embed: res }); 108 | else return message.channel.send(res); 109 | } 110 | if (message.guild !== null) { 111 | // Category/Command Disabled 112 | const DisabledDoc = client.databaseCache.getDocument( 113 | "disabledcommands", 114 | message.guild.id, 115 | ); 116 | if (DisabledDoc && DisabledDoc.commands.includes(command.name)) { 117 | const res = client.defaultResponses.getValue( 118 | language, 119 | "DISABLED_COMMAND", 120 | "", 121 | client.defaultResponses.fileData[language].DISABLED_COMMAND.embed 122 | ? { 123 | description: [ 124 | { 125 | key: "COMMAND", 126 | replace: ProperCase(command.name), 127 | }, 128 | ], 129 | } 130 | : [ 131 | { 132 | key: "COMMAND", 133 | replace: command.name, 134 | }, 135 | ], 136 | ); 137 | if (res instanceof MessageEmbed) 138 | return message.channel.send({ embed: res }); 139 | else return message.channel.send(res); 140 | } else if ( 141 | DisabledDoc && 142 | DisabledDoc.categories.includes(command.category) && 143 | !command.noDisable 144 | ) { 145 | const res = client.defaultResponses.getValue( 146 | language, 147 | "DISABLED_CATEGORY", 148 | "", 149 | client.defaultResponses.fileData[language].DISABLED_CATEGORY.embed 150 | ? { 151 | description: [ 152 | { 153 | key: "CATEGORY", 154 | replace: command.category, 155 | }, 156 | ], 157 | } 158 | : [ 159 | { 160 | key: "CATEGORY", 161 | replace: command.category, 162 | }, 163 | ], 164 | ); 165 | if (res instanceof MessageEmbed) 166 | return message.channel.send({ embed: res }); 167 | else return message.channel.send(res); 168 | } 169 | } 170 | 171 | if (message.guild !== null) { 172 | const memberPermCheck = ValidatePermissions( 173 | message.member.permissions.toArray(), 174 | command.userPermissions, 175 | ); 176 | const clientPermCheck = ValidatePermissions( 177 | message.guild.me.permissions.toArray(), 178 | command.botPermissions, 179 | ); 180 | // Client Permissions 181 | if (clientPermCheck.perms !== null) { 182 | const res = client.defaultResponses.getValue( 183 | language, 184 | "MISSING_CLIENT_PERMISSION", 185 | "", 186 | client.defaultResponses.fileData[language].MISSING_CLIENT_PERMISSION 187 | .embed 188 | ? { 189 | description: [ 190 | { 191 | key: "CLIENT_PERMISSIONS", 192 | replace: clientPermCheck.perms, 193 | }, 194 | ], 195 | } 196 | : [ 197 | { 198 | key: "CLIENT_PERMISSIONS", 199 | replace: clientPermCheck.perms, 200 | }, 201 | ], 202 | ); 203 | if (res instanceof MessageEmbed) 204 | return message.channel.send({ embed: res }); 205 | else return message.channel.send(res); 206 | } 207 | // Member Permissions 208 | if (memberPermCheck.perms !== null) { 209 | const res = client.defaultResponses.getValue( 210 | language, 211 | "MISSING_MEMBER_PERMISSION", 212 | "", 213 | client.defaultResponses.fileData[language].MISSING_MEMBER_PERMISSION 214 | .embed 215 | ? { 216 | description: [ 217 | { 218 | key: "MEMBER_PERMISSIONS", 219 | replace: memberPermCheck.perms, 220 | }, 221 | ], 222 | } 223 | : [ 224 | { 225 | key: "MEMBER_PERMISSIONS", 226 | replace: memberPermCheck.perms, 227 | }, 228 | ], 229 | ); 230 | if (res instanceof MessageEmbed) 231 | return message.channel.send({ embed: res }); 232 | else return message.channel.send(res); 233 | } 234 | } 235 | if (message.guild !== null) { 236 | // Required Roles 237 | const reqRolesDoc = client.databaseCache.getDocument( 238 | "requriedroles", 239 | message.guild.id, 240 | ); 241 | if (reqRolesDoc) { 242 | const rolesRes = ValidateRoles(reqRolesDoc, message.member, command); 243 | if (rolesRes && !message.member.permissions.has("ADMINISTRATOR")) { 244 | const res = client.defaultResponses.getValue( 245 | language, 246 | "MISSING_ROLES", 247 | "", 248 | client.defaultResponses.fileData[language].MISSING_ROLES.embed 249 | ? { 250 | description: [ 251 | { 252 | key: "ROLES", 253 | replace: `**${rolesRes.roles}**`, 254 | }, 255 | { 256 | key: "COMMAND", 257 | replace: command.name, 258 | }, 259 | ], 260 | } 261 | : [ 262 | { 263 | key: "ROLES", 264 | replace: `**${rolesRes.roles}**`, 265 | }, 266 | { 267 | key: "COMMAND", 268 | replace: command.name, 269 | }, 270 | ], 271 | ); 272 | if (res instanceof MessageEmbed) 273 | return message.channel.send({ embed: res }); 274 | else return message.channel.send(res); 275 | } 276 | } 277 | } 278 | // Developer only 279 | if (command.devOnly && !client.developers.includes(message.author.id)) { 280 | const res = client.defaultResponses.getValue( 281 | language, 282 | "DEVELOPER_ONLY", 283 | "", 284 | client.defaultResponses.fileData[language].DEVELOPER_ONLY.embed 285 | ? { 286 | description: [ 287 | { 288 | key: "COMMAND", 289 | replace: command.name, 290 | }, 291 | ], 292 | } 293 | : [ 294 | { 295 | key: "COMMAND", 296 | replace: command.name, 297 | }, 298 | ], 299 | ); 300 | if (res instanceof MessageEmbed) 301 | return message.channel.send({ embed: res }); 302 | else return message.channel.send(res); 303 | } 304 | // Test Server only 305 | if ( 306 | command.testOnly && 307 | message.guild && 308 | !client.testservers.includes(message.guild.id) 309 | ) { 310 | const res = client.defaultResponses.getValue( 311 | language, 312 | "TEST_SERVER", 313 | "", 314 | client.defaultResponses.fileData[language].TEST_SERVER.embed 315 | ? { 316 | description: [ 317 | { 318 | key: "COMMAND", 319 | replace: ProperCase(command.name), 320 | }, 321 | ], 322 | } 323 | : [ 324 | { 325 | key: "COMMAND", 326 | replace: ProperCase(command.name), 327 | }, 328 | ], 329 | ); 330 | if (res instanceof MessageEmbed) 331 | return message.channel.send({ embed: res }); 332 | else return message.channel.send(res); 333 | } 334 | // Max args 335 | if (command.maxArgs !== Infinity && args.length > command.maxArgs) { 336 | const res = client.defaultResponses.getValue( 337 | language, 338 | "TOO_MANY_ARGS", 339 | "", 340 | client.defaultResponses.fileData[language].TOO_MANY_ARGS.embed 341 | ? { 342 | description: [ 343 | { 344 | key: "USAGE", 345 | replace: `\`${command.usage.replace(/{prefix}/gi, prefix)}\``, 346 | }, 347 | ], 348 | } 349 | : [ 350 | { 351 | key: "USAGE", 352 | replace: `\`${command.usage.replace(/{prefix}/gi, prefix)}\``, 353 | }, 354 | ], 355 | ); 356 | if (res instanceof MessageEmbed) 357 | return message.channel.send({ embed: res }); 358 | else return message.channel.send(res); 359 | } 360 | // Min args 361 | if (command.minArgs !== -1 && args.length < command.minArgs) { 362 | const res = client.defaultResponses.getValue( 363 | language, 364 | "TOO_FEW_ARGS", 365 | "", 366 | client.defaultResponses.fileData[language].TOO_MANY_ARGS.embed 367 | ? { 368 | description: [ 369 | { 370 | key: "USAGE", 371 | replace: `\`${command.usage.replace(/{prefix}/gi, prefix)}\``, 372 | }, 373 | ], 374 | } 375 | : [ 376 | { 377 | key: "USAGE", 378 | replace: `\`${command.usage.replace(/{prefix}/gi, prefix)}\``, 379 | }, 380 | ], 381 | ); 382 | if (res instanceof MessageEmbed) 383 | return message.channel.send({ embed: res }); 384 | else return message.channel.send(res); 385 | } 386 | // Validate arguments 387 | if (command.validator) { 388 | const valid = await command.validator.validate({ 389 | message, 390 | args, 391 | client, 392 | prefix, 393 | language, 394 | }); 395 | if (valid !== false && typeof valid !== "string") { 396 | if (command.validator.onSuccess !== undefined) 397 | await command.validator.onSuccess(message); 398 | } else { 399 | return command.validator.onError({ 400 | message, 401 | client, 402 | prefix, 403 | args, 404 | error: typeof valid === "string" ? valid : "INVALID_ARGUMENT", 405 | language, 406 | }); 407 | } 408 | } 409 | 410 | // Global Cooldown 411 | if ( 412 | await client.cooldowns.isOnCooldown( 413 | message.author, 414 | commandName, 415 | new Date(Date.now() + command.globalCooldown), 416 | "global", 417 | ) 418 | ) { 419 | const remainingTime = client.cooldowns.getRemainingCooldown( 420 | message.author, 421 | commandName, 422 | "global", 423 | ); 424 | if (remainingTime !== undefined) { 425 | const res = client.defaultResponses.getValue( 426 | language, 427 | "GLOBAL_COOLDOWN", 428 | "", 429 | client.defaultResponses.fileData[language].GLOBAL_COOLDOWN.embed 430 | ? { 431 | description: [ 432 | { 433 | key: "COMMAND", 434 | replace: ProperCase(command.name), 435 | }, 436 | { 437 | key: "COOLDOWN", 438 | replace: FormatCooldown(remainingTime), 439 | }, 440 | ], 441 | } 442 | : [ 443 | { 444 | key: "COMMAND", 445 | replace: ProperCase(command.name), 446 | }, 447 | { 448 | key: "COOLDOWN", 449 | replace: FormatCooldown(remainingTime), 450 | }, 451 | ], 452 | ); 453 | if (res instanceof MessageEmbed) 454 | return message.channel.send({ embed: res }); 455 | else return message.channel.send(res); 456 | } 457 | } 458 | // Cooldown 459 | if ( 460 | await client.cooldowns.isOnCooldown( 461 | message.author, 462 | commandName, 463 | new Date(Date.now() + command.cooldown), 464 | "local", 465 | ) 466 | ) { 467 | const remainingTime = client.cooldowns.getRemainingCooldown( 468 | message.author, 469 | commandName, 470 | "local", 471 | ); 472 | if (remainingTime !== undefined) { 473 | const res = client.defaultResponses.getValue( 474 | language, 475 | "USER_COOLDOWN", 476 | "", 477 | client.defaultResponses.fileData[language].USER_COOLDOWN.embed 478 | ? { 479 | description: [ 480 | { 481 | key: "COMMAND", 482 | replace: command.name, 483 | }, 484 | { 485 | key: "COOLDOWN", 486 | replace: FormatCooldown(remainingTime), 487 | }, 488 | ], 489 | } 490 | : [ 491 | { 492 | key: "COMMAND", 493 | replace: command.name, 494 | }, 495 | { 496 | key: "COOLDOWN", 497 | replace: FormatCooldown(remainingTime), 498 | }, 499 | ], 500 | ); 501 | if (res instanceof MessageEmbed) 502 | return message.channel.send({ embed: res }); 503 | else return message.channel.send(res); 504 | } 505 | } 506 | if (command.globalCooldown > 0) { 507 | client.cooldowns.setCooldown( 508 | message.author, 509 | commandName, 510 | new Date(Date.now() + command.globalCooldown), 511 | "global", 512 | ); 513 | } 514 | if (command.cooldown > 0) { 515 | client.cooldowns.setCooldown( 516 | message.author, 517 | commandName, 518 | new Date(Date.now() + command.cooldown), 519 | "local", 520 | ); 521 | } 522 | 523 | return command.run({ message, args, client, prefix, language }); 524 | } 525 | }); 526 | -------------------------------------------------------------------------------- /OLD-README.MD: -------------------------------------------------------------------------------- 1 | [![](./images/cdcommands.png)](https://discord.gg/jUNbV5u) 2 | [![](https://img.shields.io/discord/769710808435261490.svg)](https://discord.gg/jUNbV5u) 3 | [![](https://img.shields.io/npm/dt/cdcommands.svg)](https://www.npmjs.com/package/cdcommands) 4 | [![](https://img.shields.io/npm/dm/cdcommands.svg?style=color=blue)](https://www.npmjs.com/package/cdcommands) 5 | [![](https://img.shields.io/npm/v/cdcommands.svg?style=color=blue)](https://www.npmjs.com/package/cdcommands) 6 | [![](https://img.shields.io/badge/license-Apache%202-blue.svg?style=flat-square)](https://github.com/CreativeDevelopments/CDCommands) 7 | [![](https://img.shields.io/github/stars/CreativeDevelopments/CDCommands?style=social)](https://github.com/CreativeDevelopments/CDCommands) 8 | 9 |
10 | 11 | # Table of Contents 12 | 13 | - [Installation](#installation) 14 | - [Initial Setup](#initial-setup) 15 | - [TypeScript Support](#typescript-support) 16 | - [Creating a Basic Command](#creating-a-basic-command) 17 | - [Command Properties](#command-properties) 18 | - [Argument Validation](#argument-validation) 19 | - [Validate](#the-validate-function) 20 | - [On Error](#the-onerror-function) 21 | - [On Success](#the-onsuccess-function) 22 | - [Default Commands](#default-commands) 23 | - [Help Command](#help-command) 24 | - [Prefix Command](#prefix-command) 25 | - [Language Command](#language-command) 26 | - [Roles Command](#roles-command) 27 | - [Commands Command](#commands-command) 28 | - [Categories Command](#categories-command) 29 | - [Creating an Event](#creating-an-event) 30 | - [Creating a Feature](#creating-a-feature) 31 | - [Default Responses](#default-responses) 32 | - [Fetching Values](#fetching-values) 33 | - [Embeds](#embeds) 34 | - [Replacers](#replacers) 35 | - [String Replacers](#string-replacers) 36 | - [Embed Replacers](#embed-replacers) 37 | - [Language Support](#language-support) 38 | - [Changing Your Language](#changing-your-language) 39 | - [Adding a New Language](#adding-a-new-language) 40 | - [Dynamic Language Support](#dynamic-language-support) 41 | - [Client Utils](#client-utils) 42 | - [Other](#other) 43 | 44 | # Installation 45 | 46 | To avoid unwanted warnings with mongoose/mongodb, we recommend installing a previous version (v5.11.15) or waiting until this issue is fixed in the next release. 47 | 48 | ``` 49 | $ npm install mongoose@5.11.15 50 | $ npm install cdcommands 51 | ``` 52 | 53 | # Initial Setup 54 | 55 | To setup your bot project with CDCommands, all you need to do is initialize the main class inside of your client ready event! 56 | 57 | ```js 58 | // ./index.js 59 | const { Client } = require("discord.js"); 60 | const { CDCommands } = require("cdcommands"); 61 | // Note: CDCommands was changed to a named export in v4.1.0 62 | 63 | const client = new Client(); 64 | 65 | client.on("ready", () => { 66 | new CDCommands(client, { 67 | // Path to your commands folder (default: "commands") 68 | commandsDir: "commands", 69 | // Path to your events folder (default: "events") 70 | eventsDir: "events", 71 | // Path to your features folder (default: "features") 72 | featuresDir: "features", 73 | // Path to message.json file (default: inside node_modules folder) 74 | MessageJSONPath: "message.json", 75 | // An array of your test servers (default: []) 76 | testServers: [], 77 | // Array of bot developers (default: []) 78 | devs: [], 79 | // The default prefix you wish to set 80 | defaultPrefix: "?", 81 | // Your mongodb connection uri 82 | mongoURI: "URI_HERE", 83 | // If you want the bot to let other bots run commands (default: true) 84 | ignoreBots: true 85 | // How frequently the cache will update the database (default: 90 seconds) 86 | cacheUpdateSpeed: 60000 * 5, 87 | // What default commands you want to "disable" or not load (default: []) 88 | disabledDefaultCommands: [], 89 | // Whether or not you want to make your own message event (default: false) 90 | customMessageEvent: false, 91 | }); 92 | 93 | console.log(`${client.user.username} has logged in!`); 94 | }); 95 | 96 | client.login("BOT_TOKEN"); 97 | ``` 98 | 99 | As long as you set everything up correctly, this should be all you technically need to get a barebones bot up and running. 100 | 101 | # TypeScript Support 102 | 103 | New in v4.1.0, CDCommands has gained support for TypeScript! You can now use one of the best command handlers with one of the best high level languages! To use CDCommands in your TypeScript project, it's as simple as changing your ES2015 imports, (require/module.exports) to use ES2016 imports and exports (import x from x/export default). For example, initializing your project in your main `index.ts` file. 104 | 105 | ```ts 106 | // ./index.ts 107 | import { Client } from "discord.js"; 108 | import { CDCommands } from "cdcommands"; 109 | // Note: CDCommands was changed to a named export in v4.1.0 110 | 111 | const client = new Client(); 112 | 113 | client.on("ready", () => { 114 | new CDCommands(client, { 115 | // Path to your commands folder (default: "commands") 116 | commandsDir: "commands", 117 | // Path to your events folder (default: "events") 118 | eventsDir: "events", 119 | // Path to your features folder (default: "features") 120 | featuresDir: "features", 121 | // Path to message.json file (default: inside node_modules folder) 122 | MessageJSONPath: "message.json", 123 | // An array of your test servers (default: []) 124 | testServers: [], 125 | // Array of bot developers (default: []) 126 | devs: [], 127 | // The default prefix you wish to set 128 | defaultPrefix: "?", 129 | // Your mongodb connection uri 130 | mongoURI: "URI_HERE", 131 | // If you want the bot to let other bots run commands (default: true) 132 | ignoreBots: true 133 | // How frequently the cache will update the database (default: 90 seconds) 134 | cacheUpdateSpeed: 60000 * 5, 135 | // What default commands you want to "disable" or not load (default: []) 136 | disabledDefaultCommands: [], 137 | // Whether or not you want to make your own message event (default: false) 138 | customMessageEvent: false, 139 | }); 140 | 141 | console.log(`${client.user.username} has logged in!`); 142 | }); 143 | 144 | client.login("BOT_TOKEN"); 145 | ``` 146 | 147 | # Creating a Basic Command 148 | 149 | Your commands folder, in this documentations case is "commands", may have as many subfolders as you wish, the handler will search through each folder and load any command that it encounters. There are six commands that are loaded by default, and these can be picked to not load in the main class under the "disabledDefaultCommands" property. Although, this is not recommended as it will remove some of the bots basic functionality, unless of course, you want to make your own versions of the command. More information about each command under [Default Commands](#default-commands)
150 | 151 | > Note: _Commands **must** be an instance of the Command class, or they will not be loaded._ A bonus of using the class is that you get powerful intellisense both while setting up your command and while creating your "run" function! 152 | 153 | To create a command, all you need is a new file in your commands directory and the Commands class from the package, then all you need to do is export a new instance of the class and you're done! New command! 154 | 155 | ```js 156 | // ./commands/Ping.js 157 | const { Command } = require("cdcommands"); 158 | 159 | module.exports = new Command(); 160 | ``` 161 | 162 | Now, obviously, this command wont work, as there are no properties associated with it, so therefore your bot cant find the command or execute it in any way. So now we will need to add some properties to this command so your bot can both recognize the command and execute its function. 163 | 164 | ## Command Properties 165 | 166 | The command class has many different properties that can help you get the most out of this handlers capabilities and to allow your bot to find and execute all the commands you create.
167 | We will also be requiring a new class from the module called "Validator", read on to learn more. 168 | 169 | ```js 170 | // ./commands/Ping.js 171 | 172 | const { Command, Validator } = require("cdcommands"); 173 | 174 | module.exports = new Command({ 175 | // The commands main name. Used to find the command. (type: string) 176 | name: "ping", 177 | // An array of command aliases that can also be used to find and 178 | // execute the command. (type: Array) 179 | aliases: ["pong"], 180 | // The category the command fits into. This is used to sort commands 181 | // for the help menu. (type: string) 182 | category: "general", 183 | // A short description of the command that is used in help menus 184 | // (type: string) 185 | description: "Gets the bots connection latency", 186 | // A more detailed description of what the command does, also is 187 | // used in help menus. (type: string) 188 | details: "View the bots current websocket and message latency", 189 | // The minimum number of arguments the command expects (type: number) 190 | minArgs: 0, 191 | // The maximum number of arguments the command expects (type: number) 192 | // Tip: Use Infinity for no maximum 193 | maxArgs: Infinity, 194 | // The expected usage of the command. Has no functionality other than 195 | // being displayed in the help menu. (type: string) 196 | // Tip: Use {prefix} to display the current guild prefix 197 | usage: "{prefix}ping", 198 | // Whether or not the command can be used only in a guild or not. 199 | // (type: boolean) 200 | guildOnly: false, 201 | // Whether or not the command can only be used in direct messages. 202 | // (type: boolean) 203 | // Tip: Set both guildOnly and dmOnly to true to "disable" a command. 204 | dmOnly: false, 205 | // Whether or not the command is NSFW, and can only be used in such 206 | // channels (type: boolean) 207 | nsfw: false, 208 | // Whether or not the command can only be used by the developers 209 | // specified in the "dev" array. (type: boolean) 210 | devOnly: false, 211 | // Whether or not the command can be disabled by the default disable 212 | // command (type: boolean) 213 | noDisable: false, 214 | // The amount of time until the same user can use said command again 215 | // (in milliseconds), use "globalCooldown" for a cross-server 216 | // global affect. (type: number) 217 | // Note: Both "cooldown" and "globalCooldown" are measured in ms 218 | cooldown: 500, 219 | // The amount of time until anyone can use said command again 220 | // (in milliseconds), use "cooldown" for a non-cross-server local 221 | // affect. (type: number) 222 | // Tip: Use 0 on either "cooldown" or "globalCooldown" for no cooldown 223 | globalCooldown: 0, 224 | // An array of permissions that the member will need to be able to run 225 | // the command. Isn't used if "dmOnly" is true. 226 | // (type: Array) 227 | userPermissions: ["SEND_MESSAGES", "EMBED_LINKS"], 228 | // An array of permissions that the bot will need to be able to run 229 | // the command. Isn't used if "dmOnly" is true. 230 | // (type: Array) 231 | botPermissions: ["SEND_MESSAGES", "EMBED_LINKS"], 232 | // More information will be provided below. (type: Validator) 233 | validate: new Validator(), 234 | /* (type: (client: CDClient) => Promise | unknown) 235 | The init method will run one time once the command is loaded. 236 | This can be used for many different things, maybe fetching some data 237 | from an API, or making sure some global variable is setup correctly. 238 | Whatever it may be, it runs once on start up. 239 | */ 240 | init(client) { 241 | console.log(`${client.user.username} from command ${this.name}.`); 242 | }, 243 | // The function that you want run when the command is used. 244 | // 5 different parameters are passed along for you to use. 245 | // message, args, client, prefix, and language 246 | /* (type: ({ message, args, client, prefix, language }: 247 | { message: Message, args: string[], client: CDClient, prefix: string, language: string }) => Promise) */ 248 | run: ({ message, args, client, prefix, language }) => { 249 | return message.channel.send(`My latency is **${client.ws.ping}ms**!`); 250 | }, 251 | }); 252 | ``` 253 | 254 | The above command, when run using either **?ping** or **?pong**, should have the bot respond with some message along the lines of: 255 | 256 | > My latency is **58ms**!
257 | 258 | ## Argument Validation 259 | 260 | The "validate" property may be slightly confusing to new users, so we decided to explain it with its own section! The validate property will accept a class called "Validator", this class provides 3 functions that you can customize to your liking, _"validate", "onSuccess", and "onError"_, each performing their own actions. Out of the three, onSuccess is optional, and the other two are required for functionality.
261 | 262 | ```js 263 | // ./commands/Ping.js 264 | const { Command, Validator } = require("cdcommands"); 265 | 266 | module.exports = new Command({ 267 | ...commandOptions, 268 | validate: new Validator({ 269 | validate: ({ message, args, client, prefix, language }) => { 270 | if (args[0] !== "test") return "INCORRECT_ARGS"; 271 | }, 272 | onError: ({ error, message, args, client, prefix, language }) => { 273 | if (error === "INCORRECT_ARGS") 274 | message.channel.send('args[0] was not equal to "test"'); 275 | }, 276 | onSuccess: (message) => { 277 | console.log('Command "ping" was run successfully!'); 278 | }, 279 | }), 280 | }); 281 | ``` 282 | 283 | Replacing the validate property shown previously with the one shown above will now validate your command! Now if you don't provide your first argument as "test", you will get a response saying `args[0] was not equal to "test"`. You now must run your command as **?ping test** or **?pong test**. 284 | 285 | ### The validate function 286 | 287 | The validate function does exactly what it sounds like it will do. It validates the command in any way that you provide in the validate function. You can return either a string, as shown above, to use in your "onError" property, or return a boolean/undefined to default to a generic error code of **"INVALID_ARGUMENT"**. Whatever string is returned from the validate function is passed into the "onError" function as the parameter "error".
288 | **Parameter Types** 289 | 290 | - `message: import("discord.js").Message` 291 | - `args: Array` 292 | - `client: CDClient` 293 | - `prefix: string` 294 | - `language: keyof import("cdcommands/src/Base/Handling/Languages.json")` 295 | 296 | ### The onError function 297 | 298 | The onError function will execute before the command is run, and terminate execution of the command. It will send whatever you wish, based on the errors created in the "validate" function. You can send whatever message you want for each error type, or just log something to your terminal. Whatever it is, it will happen before the command executes and will terminate any further execution.
299 | **Parameter Types** 300 | 301 | - `error: string` 302 | - `message: import("discord.js").Message` 303 | - `args: Array` 304 | - `client: CDClient` 305 | - `prefix: string` 306 | - `language: keyof import("cdcommands/src/Base/Handling/Languages.json")` 307 | 308 | ### The onSuccess function 309 | 310 | The onSuccess function is optional, and will execute before the command is run like the "onError" function, but unlike that function, this one is non-blocking, and will just execute the provided code before the command is run.
311 | **Parameter Types** 312 | 313 | - `message: import("discord.js").Message` 314 | 315 | ## Default Commands 316 | 317 | In CDCommands, six default commands come premade for your bots convenience, these commands consist of help, setprefix, language, requiredroles, command, and category commands. These can all be turned off or specific ones of your choosing using the "disabledDefaultCommands" property in the CDCommands class and adding each command you wish to disable to the array as a string. 318 | 319 | > Note: It is not recommended that you disable any commands unless you know what you're doing. The command will be completely unusable if you do so.
320 | 321 | ### Help Command 322 | 323 | This command can be found [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/DefaultCommands/Help.js) in our GitHub repository, and by default, the help command will be dynamic, and if necessary, partially reaction based. The default help command is decently basic, so if you wish to create your own, feel free to disable the command and style your own command to your own liking. The command is only there for convenience. The command is an ordinary help command, providing you with all the information you need to understand how to use the commands in your bot. The command will pull information from the properties set while creating each command. 324 | 325 | > Usage: **?help \** 326 | 327 | ### Prefix Command 328 | 329 | This command can be found [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/DefaultCommands/SetPrefix.js) in our GitHub repository. The prefix command, or "setprefix" is a default command that allows members with the necessary permissions to change the servers prefix that the bot will respond to. The prefix is initially stored in cache, which is used to update the database every so often. Once the prefix is updated, the bot will stop responding to the old prefix and will only respond to the new prefix. This new prefix will be passed through each command in the run parameters just like before. 330 | 331 | > Usage: **?setprefix \** 332 | 333 | ### Language Command 334 | 335 | This command can be found [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/DefaultCommands/Language.js) in our GitHub repository. The language allows you to change either your own language preference, or if you are the server owner, your whole servers language preference. More about languages can be found [here](#language-support). Your personal user preference on which language you want the bot to use will override the server default, and if neither are set, the bot will default to using the English responses. 336 | 337 | > Usage: **?language \<[ISO 639-1 Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)\>** 338 | 339 | ### Roles Command 340 | 341 | This command can be found [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/DefaultCommands/RequiredRoles.js) in our GitHub repository. The roles command, or "requiredroles" allows you to set roles in your server that a member requires to be able to use said command. Required roles can be set for any command, but if they are set on a command that has the property "dmOnly" set to true, they will have to affect on the command. If a member doesn't have the correct roles that you have set for the command, the command execution will terminate before the command is run. 342 | 343 | > Usage: **?requiredroles \ \ \** 344 | 345 | ### Commands Command 346 | 347 | This command can be found [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/DefaultCommands/Commands.js) in our GitHub repository. The commands command, though its name might be slightly confusing, is quite simple, allowing you to disable and enable different commands. Any command with the property "noDisable" set to true, will not be able to be disabled by anyone, not even bot developers. This is to prevent dumb issues like disabling the command that allows you to re-enable the same command. Disabled commands will not be able to be used by anyone. 348 | 349 | > Usage: **?command \ \** 350 | 351 | ### Categories Command 352 | 353 | This command can be found [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/DefaultCommands/Categories.js) in our GitHub repository. Similarly to the commands command, the category command allows you to enable and disable entire categories instead of just certain commands. Just like the commands command again, if a command is inside of a disabled category, but has the "noDisable" property set to true, the command will still work, as it is not capable of being disabled. Any command in a disabled category otherwise will not be able to be used by anyone. 354 | 355 | > Usage: **?category \ \** 356 | 357 | # Creating an Event 358 | 359 | To create a new event for your bot to listen to, all you need to do is create a new file in your events directory, which in this case is "events", and import the class named "Event" from the module. After that, all you need to do is export a new instance of the class and there you have it! A new event file! 360 | 361 | ```js 362 | // ./events/messageDelete.js 363 | const { Event } = require("cdcommands"); 364 | 365 | module.exports = new Event(); 366 | ``` 367 | 368 | Now, again, of course this event wont do anything for you, so we need to add a couple properties to the class. We are going to assign an event name, which is the same as assigning an event name in your regular `#on` listener, then we will also assign a callback function, which will have your client as `CDClient` as the first parameter, followed by the parameters that the regular listener has. 369 | 370 | > Note: Event files **must** use the Event class or else they will not be loaded. A bonus of using the class is that you get powerful intellisense as if you were using a regular client listener! 371 | 372 | ```js 373 | // ./events/messageDelete.js 374 | const { Event } = require("cdcommands"); 375 | 376 | module.exports = new Event("messageDelete", (client, message) => { 377 | console.log( 378 | `${client.user.username} saw ${message.author.username} delete ${message.content}`, 379 | ); 380 | }); 381 | ``` 382 | 383 | The above event, once your bot logs in, should log a message along the lines of `Application Name saw User delete Test` every time a user that the bot has access to deletes a message from any channel the bot can see. Since we are using the "messageDelete" event in this example, the parameters in the callback function are expected to be first your client, then followed by the message object that was deleted. 384 | 385 | > Note: A message event file is loaded by default to allow all the different checks for permissions and others to work. The loading of this event can be disabled by setting the "customMessageEvent" property to **true** in the CDCommands class, though it is not recommended as it will break a lot of your commands functionality. If you want to make your own message event you can use this template [here](https://sourceb.in/CHesdx5xJa) 386 | 387 | # Creating a Feature 388 | 389 | Features are quite simple, they are loaded and run one time before your bot starts, but after all your commands and events load in. As of now there is no way to re-run features after startup, but there may be sometime in the future. Creating a Feature is extremely simple, just like everything else so far. All you need to do is create a new file in your features directory, in this case it will be "features". This file can have whatever name you like, and all you need to do is import the "Feature" class from the module, and export a new instance of the class. 390 | 391 | ```js 392 | // ./features/file_name.js 393 | const { Feature } = require("cdcommands"); 394 | 395 | module.exports = new Feature(); 396 | ``` 397 | 398 | Just like with your commands and your events, a feature set up in this way will do nothing for you. All we need to do to set it up is add a single callback function as a parameter in the class. This callback function will have a single parameter in it, which will be the client as CDClient. 399 | 400 | ```js 401 | // ./features/file_name.js 402 | const { Feature } = require("cdcommands"); 403 | 404 | module.exports = new Feature((client) => { 405 | console.log( 406 | `This is ${client.user.username} from "./features/file_name.js"!`, 407 | ); 408 | }); 409 | ``` 410 | 411 | The above feature is extremely basic, and doesn't really do much for you in terms of functionallity, but it should log something along the lines of `This is Application Name from "./features/file_name.js"!` in your console. You can do whatever you want in this file, and it will be run along with your bot starting up. You can add your own listeners here, although we would recommend using an Event instead, or you could start an Interval for updating mutes in your servers. 412 | 413 | > Note: Features will not be run if they are not created with the "Feature" class. 414 | 415 | # Default Responses 416 | 417 | At this point, you should have a very basic, barebones bot up and running, and the first time you started your bot, you should have noticed something that was console logged that said something along the lines of `[Success] .vscode/settings.json has been initialized, you can now use intellisense with your message.json file!`. All this message is saying is that now that your project has been run, the handler has had a chance to initialize your ability to use intellisense with any message.json file that you create in your project directory. 418 | 419 | ## What does a message.json file do? 420 | 421 | Your message.json file is completely optional, as one is used by default in the module folder, but it allows you to create your own responses for commands that can be fetched throughout your code. These responses can be whatever you wish, and if you don't want to make your own responses, you can either exclude the "MessageJSONPath" property from the CDCommands class, or get the default message.json file from [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/message.json). This file will include all of the default command responses and any permission check responses that are triggered in the message event. 422 | 423 | ## Fetching Values 424 | 425 | To fetch these responses in your code, a convenient property has been added to your client object, the "defaultResponses" property. This property only has one method, which consists of **defaultResponses#getValue**. For the example below, say we setup a new message.json file with extra properties as follows... 426 | 427 | ```json 428 | // ./message.json 429 | { 430 | "en": { 431 | ...defaultValues, 432 | "TEST_COMMAND": "This is a response from the message.json file!" 433 | } 434 | } 435 | ``` 436 | 437 | To fetch this value from our code inside of a command, all we need to do is call the getValue method with the appropriate information. For now we can ignore the ISO codes, as we will talk more about them in [Language Support](#language-support). For this example we will just continue to use the Ping.js command we made earlier. 438 | 439 | ```js 440 | // ./commands/Ping.js 441 | const { Command, Validator } = require("cdcommands"); 442 | 443 | module.exports = new Command({ 444 | ...commandOptions, 445 | run: ({ message, args, client, prefix, language }) => { 446 | const message_json_response = client.defaultResponses.getValue( 447 | "en", 448 | "TEST_COMMAND", 449 | "", 450 | [], 451 | ); // In this example, getValue will return a string. 452 | // getValue can also return a MessageEmbed more information in Embeds. 453 | return message.channel.send(message_json_response); 454 | }, 455 | }); 456 | ``` 457 | 458 | The above code, when run with **?ping** or **?pong**, should respond with the content as `This is a response from the message.json file!`, meaning it successfully read from your message.json file! But you may be asking, what is that extra empty string doing there? What is that empty array there for? What is "en" doing there? Don't worry, these will all be answered, for now we can focus on the second empty string. Why is it there? This secondary string, or key in the context of the message.json file, will read values inside of an object in the message.json file. With this, you have the ability to setup responses under a main category of responses. To do this, all you need to change is the "TEST_COMMAND" property in your message.json file to an object. In this example we will add two values to the object. 459 | 460 | ```json 461 | // ./message.json 462 | { 463 | "en": { 464 | ...defaultValues, 465 | "TEST_COMMAND": { 466 | "TEST_VALUE_ONE": "This is the first test value under \"TEST_COMMAND\"", 467 | "TEST_VALUE_TWO": "This is the second test value under \"TEST_COMMAND\"" 468 | } 469 | } 470 | } 471 | ``` 472 | 473 | To access the two values in TEST_COMMAND now, we will use an almost identical format for the getValue method, only adding one extra value in the place of where the extra string was. 474 | 475 | ```js 476 | // ./commands/Ping.js 477 | const { Command, Validator } = require("cdcommands"); 478 | 479 | module.exports = new Command({ 480 | ...commandOptions, 481 | run: ({ message, args, client, prefix, language }) => { 482 | const json_response_v_one = client.defaultResponses.getValue( 483 | "en", 484 | "TEST_COMMAND", 485 | "TEST_VALUE_ONE", 486 | [], 487 | ); 488 | const json_response_v_two = client.defaultResponses.getValue( 489 | "en", 490 | "TEST_COMMAND", 491 | "TEST_VALUE_TWO", 492 | [], 493 | ); 494 | 495 | message.channel.send(json_response_v_one); 496 | message.channel.send(json_response_v_two); 497 | }, 498 | }); 499 | ``` 500 | 501 | The above code snippet should respond with first the message `This is the first test value under "TEST_COMMAND"` then the message `This is the second test value under "TEST_COMMAND"`. The first string acts as the language that you want to look into in your message.json file, which you can find more information for under [Language Support](#language-support), the second string is the first key that you are getting values by, then depending on if the value received by the first key is an object or a string, the second key will either be the next property you want to get or an empty string respectively, as shown in the last two examples. The last value can either be an object or an array of objects, which will act as your replacers. More information on replacers can be found under [Replacers](#replacers). 502 | 503 | ## Embeds 504 | 505 | The next concept that we would like to cover about your message.json file, is adding embeds directly in your file. To do this, it should be quite simple if you have run your project at least once to allow intellisense in the file to be setup, but we shall give you an example of adding embeds to your file. To do so, we need to set the value as an object, and add the property of "embed", and set its value to an object. For this example we will use the same initial value as above. 506 | 507 | ```json 508 | // ./message.json 509 | { 510 | "en": { 511 | ...defaultValues, 512 | "TEST_COMMAND": { 513 | "embed": {} 514 | } 515 | } 516 | } 517 | ``` 518 | 519 | If you were to leave the value as an empty object, then you would get an empty embed in return when getting this value. To actually add values, we need to add actual message embed properties to the object. We will provide a simple example for every property. 520 | 521 | > Note: All properties supported in the embed consist of "author", "color", "description", "fields", "footer", "image", "thumbnail", "timestamp", "title", and "url". 522 | > Important: The "fields" value will only support from 1 to 25 field objects. 523 | 524 | ```json 525 | // ./message.json 526 | { 527 | "en": { 528 | ...defaultValues, 529 | "TEST_COMMAND": { 530 | "embed": { 531 | "author": { 532 | "name": "Author Name", 533 | "iconURL": "https://discord.com/assets/1cbd08c76f8af6dddce02c5138971129.png" 534 | }, 535 | "color": "RED", 536 | "description": "This is an embed description from message.json", 537 | "fields": [ 538 | { 539 | "name": "Field Name", 540 | "value": "Field Value", 541 | "inline": false 542 | } 543 | ], 544 | "footer": { 545 | "text": "This is the footer text", 546 | "iconURL": "https://discord.com/assets/1cbd08c76f8af6dddce02c5138971129.png" 547 | }, 548 | "image": { 549 | "url": "https://discord.com/assets/1cbd08c76f8af6dddce02c5138971129.png" 550 | }, 551 | "thumbnail": { 552 | "url": "https://discord.com/assets/1cbd08c76f8af6dddce02c5138971129.png" 553 | }, 554 | "timestamp": 343245323523, 555 | "title": "This is a title that goes to google.com", 556 | "url": "https://www.google.com" 557 | } 558 | } 559 | } 560 | } 561 | ``` 562 | 563 | To fetch this data from your message.json file, you would need to use the same method as shown first, except for the fact that the [replacer parameter](#replacers) will need to be an object instead of an array of objects. More information in [Replacers](#replacers). 564 | 565 | ```js 566 | // ./commands/Ping.js 567 | const { Command, Validator } = require("cdcommands"); 568 | 569 | module.exports = new Command({ 570 | ...commandOptions, 571 | run: ({ message, args, client, prefix, language }) => { 572 | const message_json_response = client.defaultResponses.getValue( 573 | "en", 574 | "TEST_COMMAND", 575 | "", 576 | {}, 577 | ); // getValue will now return a MessageEmbed, so you will need to handle it correctly 578 | // depending on how you have your message.json file setup 579 | return message.channel.send({ embed: message_json_response }); 580 | }, 581 | }); 582 | ``` 583 | 584 | The above snippet should return a message similar to the one shown in this image.
585 | ![](./images/embed.png) 586 | 587 | ## Replacers 588 | 589 | The final thing we need to cover in the getValue method, provided by the defaultResponses, is the replacers parameter. This parameter can be either an array of objects or an object with embed values in them. We will provide two examples, one of each, to provide as much information as possible. Replacers in your message.json file are identified by brackets. Any value in-between `{}` will be treated as a replacer. In general, replacers are only needed if you have a dynamic value that will change from time to time, say if a different user uses the command, or you want to provide a random response. 590 | 591 | ### String Replacers 592 | 593 | If the value fetched in the getValue method is a string, you should use an array to list the replacers you want to use. Say we have the message.json key value pair below. The replacer in the example will be `REPLACER`. 594 | 595 | ```json 596 | // ./message.json 597 | { 598 | "en": { 599 | ...defaultValues, 600 | "SOME_VALUE": "This is a replacer value from message.json. Replace here -> {REPLACER}" 601 | } 602 | } 603 | ``` 604 | 605 | We can go ahead and fetch this value just like before, but instead of leaving an empty array as the last parameter, we can fill it out with the information needed to replace the value with a different value. 606 | 607 | ```js 608 | // ./commands/Ping.js 609 | const { Command, Validator } = require("cdcommands"); 610 | 611 | module.exports = new Command({ 612 | ...commandOptions, 613 | run: ({ message, args, client, prefix, language }) => { 614 | const message_json_response = client.defaultResponses.getValue( 615 | "en", 616 | "SOME_VALUE", 617 | "", 618 | [ 619 | { 620 | key: "REPLACER", 621 | replace: "This is the replaced value!", 622 | }, 623 | ], 624 | ); 625 | return message.channel.send(message_json_response); 626 | }, 627 | }); 628 | ``` 629 | 630 | The above snippet of code should respond with something along the lines of `This is a replacer value from message.json. Replace here -> This is the replaced value!`. You are allowed to include as many replacers as needed, but usually only one is needed. There are some provided replacers that intellisense will try to recommend to you, none of them are required to be used, you can use whatever replacer value you want to. 631 | 632 | ### Embed Replacers 633 | 634 | Embed replacers are slightly different as you need to provide replacers for every value that you have replacers setup in the embed in your message.json. The parameter if the message.json value is an embed will instead be an object instead of an array of objects. For the example below we will need to add a replacer for all the text values. 635 | 636 | ```json 637 | // ./message.json 638 | { 639 | "en": { 640 | ...defaultValues, 641 | "SOME_VALUE": { 642 | "embed": { 643 | "author": { 644 | "name": "{AUTHOR_REPLACER}", 645 | "iconURL": "https://discord.com/assets/1cbd08c76f8af6dddce02c5138971129.png" 646 | }, 647 | "color": "{COLOR_DYNAMIC}", 648 | "description": "This is an embed description from message.json, {REPLACE_ME}", 649 | "fields": [ 650 | { 651 | "name": "{DYNAMIC_FIELD_NAME}", 652 | "value": "This is a test! {VALUE}", 653 | "inline": false 654 | } 655 | ], 656 | "footer": { 657 | "text": "This is the footer text! {EXTRA_TEXT}", 658 | "iconURL": "https://discord.com/assets/1cbd08c76f8af6dddce02c5138971129.png" 659 | }, 660 | "image": { 661 | "url": "https://discord.com/assets/1cbd08c76f8af6dddce02c5138971129.png" 662 | }, 663 | "thumbnail": { 664 | "url": "https://discord.com/assets/1cbd08c76f8af6dddce02c5138971129.png" 665 | }, 666 | "timestamp": 343245323523, 667 | "title": "This is a title that goes to google.com, {MORE_TITLE}", 668 | "url": "https://www.google.com" 669 | } 670 | } 671 | } 672 | } 673 | ``` 674 | 675 | ```js 676 | // ./commands/Ping.js 677 | const { Command, Validator } = require("cdcommands"); 678 | 679 | module.exports = new Command({ 680 | ...commandOptions, 681 | run: ({ message, args, client, prefix, language }) => { 682 | const message_json_response = client.defaultResponses.getValue( 683 | "en", 684 | "SOME_VALUE", 685 | "", 686 | { 687 | author_name: [ 688 | { 689 | key: "AUTHOR_REPLACER", 690 | replace: message.author.username, 691 | }, 692 | ], 693 | color: [ 694 | { 695 | key: "COLOR_DYNAMIC", 696 | replace: "#FFFFFF", 697 | }, 698 | ], 699 | description: [ 700 | { 701 | key: "REPLACE_ME", 702 | replace: "I have been replaced!", 703 | }, 704 | ], 705 | fields: [ 706 | /* The fields array is slightly different from the rest 707 | as it has you use replacers for each value of each field 708 | for name, value, and inline (none of which are "required"). 709 | */ 710 | { 711 | name: [ 712 | { 713 | key: "DYNAMIC_FIELD_NAME", 714 | replace: "Some dynamic value!", 715 | }, 716 | ], 717 | value: [ 718 | { 719 | key: "VALUE", 720 | replace: "Hey!", 721 | }, 722 | ], 723 | }, 724 | ], 725 | footer_text: [ 726 | { 727 | key: "EXTRA_TEXT", 728 | replace: "This is extra text", 729 | }, 730 | ], 731 | title: [ 732 | { 733 | key: "MORE_TITLE", 734 | replace: "This is more title", 735 | }, 736 | ], 737 | }, 738 | ); 739 | return message.channel.send({ embed: message_json_response }); 740 | }, 741 | }); 742 | ``` 743 | 744 | As you can see from the above code, replacing values in every single field can get extremely space inefficient, so for that reason, it isn't recommended unless you need to do so. The option is just there if you need to use it. Using this feature would also be more efficient if you had dynamic values that needed to be different on every single run, so you could replace part of the message to the updated value. Now, if you were to run the above snippet of code, you should see a new embed that is sent that somewhat resembles the image below.
745 | ![](./images/embed_2.png) 746 |
747 | 748 | > Note: In general, this feature is only recommended to be used if you want to use multiple languages for your bot, more information [here](#language-support), or you need a dynamic value that also has constant text in it. 749 | 750 | ## Language Support 751 | 752 | In your message.json, you probably noticed that the initial property is the ISO code for English, along with other default supported languages ISO codes. This allows you to create multiple versions of the same default responses to allow your bot to support multiple languages. To support multiple languages, the handler will store guild based and user based preferences about the language the bot should use, which can be changed with the [language command](#the-language-command). By default, the default message.json file supports the languages shown [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/SUPPORTED_LANGS.md), but you can add any language that you wish to. 753 | 754 | ### Changing Your Language 755 | 756 | To change your language, either for your server, or for yourself, its quite simple. All you need to do is run the [language command](#the-language-command) with a valid ISO code. If you are the guild owner, the command will automatically change the server wide preferences, but if you are just a member of the server, then it will change your specific user preferences. Specific user language preferences, if they exists, will override server language preferences. You can change your language to any valid [ISO 639-1 Code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) that is currently supported by the bot developer. As in, only if the code provided is a valid code that also exists in the bot developers message.json file. 757 | 758 | ### Adding a New Language 759 | 760 | Adding a new language to your message.json file should be a simple process, as long as you have someone that can translate for you. If needed, our [default message.json](https://github.com/CreativeDevelopments/CDCommands/blob/main/SUPPORTED_LANGS.md) file already supports the languages that have a :white_check_mark: beside them. You can get the file [here](https://github.com/CreativeDevelopments/CDCommands/blob/main/src/Base/message.json). If you wish to add your own languages instead, we will now show you how to! First we need to go to our message.json file, then we can now take a look at the primary property. "en". What is that? Well, it's an ISO 639-1 Code, like mentioned before. It's the standard way to list languages in a two character format. Each new language that you add will be a new ISO code in your message.json file. 761 | 762 | ```json 763 | // ./message.json 764 | { 765 | "en": { 766 | ...defaultValues, 767 | "TESTING": "This is a response from the message.json file!" 768 | } 769 | } 770 | ``` 771 | 772 | So we have our values all stored in our message.json file like shown above, pretty simple layout, and only one extra property. Now, when creating a new language, it is recommended that you translate all of the same values from one language to the other language, or else you will most likely encounter errors or unwanted behaviour. To add the new language it's as easy as adding a new property to the file like so. 773 | 774 | ```json 775 | // ./message.json 776 | { 777 | "en": { 778 | ...defaultValues, 779 | "TESTING": "This is a response from the message.json file!" 780 | }, 781 | "es": { 782 | ...defaultValues_translated, 783 | "TESTING": "¡Esta es una respuesta del archivo message.json!" 784 | } 785 | } 786 | ``` 787 | 788 | The above translation was done using Google Translate, so it may not be entirely accurate, but you should get the idea. We added a new language, in this case Spanish (es), and added all of the properties that English had and translated them into Spanish. You can add as many language codes as you want as long as you can translate every property value into the other language. 789 | 790 | ### Dynamic Language Support 791 | 792 | Now to actually use these languages, you can either hard type the language that you want to use, or you can use the language command to setup which language you or your guild would like to use, then use the "language" parameter that is passed into all of your commands "run" methods. 793 | 794 | ```js 795 | // ./commands/Ping.js 796 | const { Command, Validator } = require("cdcommands"); 797 | 798 | module.exports = new Command({ 799 | ...commandOptions, 800 | run: ({ message, args, client, prefix, language }) => { 801 | const message_json_response = client.defaultResponses.getValue( 802 | "es", 803 | "TESTING", 804 | "", 805 | [], 806 | ); 807 | return message.channel.send(message_json_response); 808 | }, 809 | }); 810 | ``` 811 | 812 | The above snippet of code will only ever return the Spanish translation of the json value. If you wish to dynamically get a users language, all you need to do is use the language parameter shown below! All of the database work is done for you behind the scenes! 813 | 814 | ```js 815 | // ./commands/Ping.js 816 | const { Command, Validator } = require("cdcommands"); 817 | 818 | module.exports = new Command({ 819 | ...commandOptions, 820 | run: ({ message, args, client, prefix, language }) => { 821 | const message_json_response = client.defaultResponses.getValue( 822 | language, // The only value that needs to change 823 | "TESTING", 824 | "", 825 | [], 826 | ); 827 | return message.channel.send(message_json_response); 828 | }, 829 | }); 830 | ``` 831 | 832 | Now with the above code, you should get a different response depending on what the server/user language preference is set to! Easy as that. 833 | 834 | # Client Utils 835 | 836 | This is a general list of most of the added functionality to the "CDClient" client by CDCommands. This is not a complete list, but most of the properties/methods that exist on the client that aren't mentioned here don't need to be messed with. 837 | 838 | ### #getLanguage() 839 | 840 | This basic method accepts one parameter as an object with a users ID and guild ID, which will return the corresponding language to be used. If neither the guild or user have a language set, "en" is used and returned by default. This method doesn't really need to be used, as most of the place that you would need it, the language is passed to. 841 | 842 | ### #databaseCache 843 | 844 | The database cache is a system created for the models and documents that are required for CDCommands to function correctly, this allows us to cache database information, allowing us to fetch it faster and call the database less. The database is updated every x amount of time, specified by the "cacheUpdateSpeed" property. This is not normally needed for end user use, but is there if you need it. (It is recommended to use this instead of requiring the models from the module) 845 | 846 | ### #defaultResponses 847 | 848 | This is talked about in more detail under [Default Responses](#default-responses). This property allows you to set up a message.json file and use responses from the file, allowing you to set up a multi-lingual bot. The property allows for easy fetching and replacing of values in the response. 849 | 850 | --- 851 | 852 | > Note: The below methods have no functionality other than returning a MessageEmbed as a convenience method. 853 | 854 | ### #load() 855 | 856 | The load method will return a message [#00DCFF](https://www.google.com/search?q=%2300dcff) color and the description set to the provided string. 857 | 858 | ### #error() 859 | 860 | The error method will return a message [#C93131](https://www.google.com/search?q=%23C93131) color and the description set to the provided string. 861 | 862 | ### #success() 863 | 864 | The success method will return a message [#2FDD2C](https://www.google.com/search?q=%232FDD2C) color and the description set to the provided string. 865 | 866 | ### #info() 867 | 868 | The info method will return a message [#00DCFF](https://www.google.com/search?q=%2300dcff) color and the description set to the provided string. 869 | 870 | --- 871 | 872 | > Note: The below methods provide no functionality other than logging messages to your terminal with adjusted colors. 873 | 874 | ### #logReady() 875 | 876 | The logReady method will log your message with an appended "[READY]" at the start of your message colored in green. 877 | 878 | ### #logInfo() 879 | 880 | The logInfo method will log your message with an appended "[INFO]" at the start of your message colored in cyan. 881 | 882 | ### #logError() 883 | 884 | The logError method will log your message with an appended "[ERROR]" at the start of your message colored in red. 885 | 886 | ### #logWarn() 887 | 888 | The logWarn method will log your message with an appended "[WARN]" at the start of your message colored in yellow. 889 | 890 | ### #logDatabase() 891 | 892 | The logDatabase method will log your message with an appended "[DATABASE]" at the start of your message colored in green. 893 | 894 | # Other 895 | 896 | If you have any suggestions, bugs or need some help setting it up please join our [Support Server](https://discord.com/invite/jUNbV5u). 897 | You can see what we are adding next on the [to do list](https://github.com/CreativeDevelopments/CDCommands/blob/main/TODO.md) on our GitHub repository. 898 | --------------------------------------------------------------------------------