diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index f59ec20a..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -* \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bf89e8a5..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "@axonteam/eslint-config", - "rules": { - "no-magic-numbers": "off" - } -} diff --git a/.gitignore b/.gitignore index 83decfa7..c7328199 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,11 @@ -.DS_Store +build node_modules -*.code-workspace -package-lock.json -configs/secret.json -src/index.js -src/modules/Info/listeners/index.js + .env -# Local directories/files -Archives -src/modules/Games/data.csv -src/assets/wordle/wordledata.csv +# IDE Files +*.vscode +*.code-workspace -.npmrc +# Misc +.DS_Store \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1ad3c73a..ee7e6e8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,10 @@ "version": "3.0.0", "license": "ISC", "dependencies": { - "discord.js": "^14.14.1" - }, - "devDependencies": { - "@types/node": "^20.11.25" + "async-each": "^1.0.6", + "discord.js": "^14.14.1", + "mongoose": "^8.2.2", + "mongoose-long": "^0.8.0" } }, "node_modules/@discordjs/builders": { @@ -121,6 +121,14 @@ "node": ">=14" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", + "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@sapphire/async-queue": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.2.tgz", @@ -159,6 +167,19 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.9", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", @@ -176,6 +197,41 @@ "npm": ">=7.0.0" } }, + "node_modules/async-each": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/bson": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.5.0.tgz", + "integrity": "sha512-DXf1BTAS8vKyR90BO4x5v3rKVarmkdkzwOrnYDFdjAY694ILNDkmA3uRh1xXJEl+C1DAh8XCvAQ+Gh3kzubtpg==", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/discord-api-types": { "version": "0.37.61", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", @@ -210,6 +266,14 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -225,6 +289,155 @@ "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==" }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "node_modules/mongodb": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.2.2.tgz", + "integrity": "sha512-6sMxe1d3k/dBjiOX4ExNTNOP0g1x0iq8eXyg+ttgIXM3HLnQ0IUyXRwVVAPFFY6O4/8uYN5dB0Ec72FrexbPpw==", + "dependencies": { + "bson": "^6.2.0", + "kareem": "2.5.1", + "mongodb": "6.3.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose-long": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/mongoose-long/-/mongoose-long-0.8.0.tgz", + "integrity": "sha512-gNpLDs9HE5W5rKuZPFOzFB1Ei/SJbSSA6HlF/wR5qGU3N9K5SXfXlvWm+uUWpqZ13H3Zhna3Ww6ILGW4TjFP+g==", + "peerDependencies": { + "mongoose": "4.x || 5.x || 6.x || 7.x || 8.x" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/ts-mixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", @@ -251,6 +464,26 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/ws": { "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", diff --git a/package.json b/package.json index e1562657..23c1f887 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "author": "Kyle S (nonsequential)", "description": "A multi-purpose Discord application", "main": "build/start.js", + "typings": "src/index.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -17,14 +18,13 @@ }, "keywords": [ "Discord", - "Discord", - "Bot" + "Discord Bot" ], "dependencies": { - "discord.js": "^14.14.1" - }, - "devDependencies": { - "@types/node": "^20.11.25" + "async-each": "^1.0.6", + "discord.js": "^14.14.1", + "mongoose": "^8.2.2", + "mongoose-long": "^0.8.0" }, "license": "ISC" } diff --git a/src/Core/Config.ts b/src/Core/Config.ts new file mode 100644 index 00000000..0fe1e6b0 --- /dev/null +++ b/src/Core/Config.ts @@ -0,0 +1,53 @@ +import Naga from '../../'; +import * as djs from 'discord.js'; + +const pkg = require('../../package.json'); + +let config: any = { + name: pkg.name.charAt(0).toUpperCase() + pkg.name.slice(1), + version: pkg.version, + author: 'Kyle S', + prefix: 'n.', + adminPrefix: '$', + state: 1341, + stateName: 'Development', + client: { + id: '966189242969636866', + token: process.env.TOKEN, + options: { + maxShards: 1, + allowedMentions: { parse: [ 'roles'] }, + fetchAllUsers: true, + intents: process.env.INTENTS as unknown as djs.IntentsBitField, + messageCacheSize: 5000, + } + }, + database: { + uri: process.env.DB_CONNECTION, + }, + emojis: { + success: '', + error: '', + info: '' + }, + status: [ + { name: 'Testing', type: 0 }, + { name: 'Naga v3', type: 0 }, + { name: 'n.help', type: 0 } + ], + avatarGuild: '370708369951948800', + testGuilds: [ + '546800805060280352', // Quantum Biotics, Inc. (soda) + '504742982604554241' // TwoDog Fan Club + ], + developers: [ + '254814547326533632', // Soda + '123261299864895489' // TwoDog + ], + contributors: [ + '260600155630338048' // Avatar Bolin + ], + pkg: pkg, +}; + +export default config; \ No newline at end of file diff --git a/src/Core/Logger.ts b/src/Core/Logger.ts new file mode 100644 index 00000000..397cbc54 --- /dev/null +++ b/src/Core/Logger.ts @@ -0,0 +1,52 @@ +import path from 'path'; + +export default class Logger { + private errorRegex = /\((.*):(\d+):(\d+)\)$/; + + public fileName(file: any): string { + return path.parse(file).name; + } + + public callerLocation(): any { + const e: any = new Error(); + const regex = /\((.*):(\d+):(\d+)\)$/; + const match: any = regex.exec(e.stack.split("\n")[2]); + return match[1]; + } + + public info(message: string, prefix?: string) { + if (prefix === undefined) { + const e: any = new Error(); + const match: any = this.errorRegex.exec(e.stack.split("\n")[2]); + prefix = this.fileName(match[0]) + } + return console.info(`[${prefix}] ${message}`); + } + + public debug(message: string, prefix?: string) { + if (prefix === undefined) { + const e: any = new Error(); + const match: any = this.errorRegex.exec(e.stack.split("\n")[2]); + prefix = this.fileName(match[0]) + } + return console.debug(`[${prefix}] ${message}`); + } + + public error(message: Error|string, prefix?: string) { + if (prefix === undefined) { + const e: any = new Error(); + const match: any = this.errorRegex.exec(e.stack.split("\n")[2]); + prefix = this.fileName(match[0]) + } + return console.error(`[${prefix}] ${message}`); + } + + public log(message: string, prefix?: string) { + if (prefix === undefined) { + const e: any = new Error(); + const match: any = this.errorRegex.exec(e.stack.split("\n")[2]); + prefix = this.fileName(match[0]) + } + return console.log(`[${prefix}] ${message}`); + } +} \ No newline at end of file diff --git a/src/Core/Naga.ts b/src/Core/Naga.ts index 5d940097..98c58e28 100644 --- a/src/Core/Naga.ts +++ b/src/Core/Naga.ts @@ -1,28 +1,72 @@ import * as djs from 'discord.js'; - -import Utils from '../Structures/Utils'; +import { GlobalConfig } from '../'; +import config from './Config'; +import Logger from './Logger'; +import { Utils } from '../Structures'; import PermissionsManager from '../Managers/PermissionsManager'; +import CommandManager from '../Managers/CommandManager'; +import GuildManager from '../Managers/GuildManager'; +import ModuleManager from '../Managers/ModuleManager'; + +import SpiritLibrary from './Database/Database'; + +var name = 'BotClient'; +var nagaConfig = config; export default class Naga { - private _client!: djs.Client; + private _client: djs.Client; public isReady: boolean; - + public _globalConfig: GlobalConfig; public _utils: Utils; + public _logger: Logger; public permissions: PermissionsManager; + public commands: CommandManager; + public guilds: GuildManager; + public modules: ModuleManager; + public _db: SpiritLibrary; + public _globalConfigInterval: NodeJS.Timeout; + public isLoading: any; constructor() { this.isReady = false; } + // discord.js client public get client() { return this._client; } + // bot config + public get config() { + return config; + } + + // global config + public get globalConfig() { + return this._globalConfig; + } + + // common utility functions public get utils() { return this._utils; } + // logger instance + public get logger() { + return this._logger; + } + + // mongo instance + public get db() { + return this._db; + } + + // mongo models + public get models() { + return this.db.models; + } + /** * Initializes the bot client * @param opts Client options @@ -33,28 +77,83 @@ export default class Naga { options.allowedMentions = opts.allowedMentions || { parse: [ 'roles'] }; options.intents = opts.intents || [djs.GatewayIntentBits.Guilds]; + this._db = new SpiritLibrary(this.config.database.uri); + this._logger = new Logger(); + this._utils = new Utils(); + // Initialize the discord.js client + this.logger.info('Initializing client', name); this._client = new djs.Client(options); // Initialize manager classes this.permissions = new PermissionsManager(this); + this.commands = new CommandManager(this); + this.modules = new ModuleManager(this); + this.guilds = new GuildManager(this); // Event functions this.client.once('ready', this.ready.bind(this)); - this.client.on('error', err => console.error(err)) + this.client.on('error', err => this.logger.error(err)); + + this.client.guilds.cache.forEach(g => { + this.loadGuild(g.id); + }) + + await this.loadConfig().catch(() => null); + await this.watchGlobalConfig(); // Connect to Discord const token = process.env.TOKEN as string; await this.login(token); } + /** + * Loads config from the db + */ + public async loadConfig(): Promise { + try { + this.logger.info('Loading config', name); + if (this.models.Config != undefined) { + const dbConfig = await this.models.Config.findOne({ clientId: this.config.client.id }).lean() + if (dbConfig) { + nagaConfig = Object.assign(config, dbConfig); + } + this.logger.info('Loading globalConfig', name) + const globalConfig: GlobalConfig = await this.models.Naga.findOne().lean(); + this._globalConfig = config.global = globalConfig; + } + } catch (err) { + this.logger.error(`Unable to load config: ${err}`, name); + } + } + + async watchGlobalConfig() { + await this.updateGlobalConfig(); + this._globalConfigInterval = setInterval(() => this.updateGlobalConfig(), 2 * 60 * 1000); + } + + /** + * Updates the global config db doc + */ + public async updateGlobalConfig(): Promise { + try { + this.logger.info('Updating globalConfig') + const globalConfig: GlobalConfig = await this.models.Naga.findOne().lean(); + if (globalConfig) { + this._globalConfig = config.global = globalConfig; + } + } catch (err) { + this.logger.error(`Unable to update globalConfig: ${err}`, name); + } + } + /** * Connects to the Discord gateway * @param t Bot token */ public async login(t: string): Promise { const token = 'Bot ' + t; - console.info('Connecting to Discord...'); + this.logger.info('Connecting to Discord', name); await this.client.login(token); } @@ -71,9 +170,39 @@ export default class Naga { * Executes when the bot fires the "ready" event */ public async ready(): Promise { - console.log(`Successfully connected to Discord as ${this.client.user!.username}`) + this.logger.info(`Connected to Discord as ${this.utils.fullName(this.client.user)}`, name); this.isReady = true; await this.editStatus({name: 'Testing', type: 0}) } + + public loadGuild(id: string) { + + let g = this.client.guilds.cache.get(id); + + this.guilds.getOrFetch(g.id); + const p = new Promise(async (res, rej) => { + try { + let [guild, channels] = await Promise.all([ + this.client.guilds.cache.get(id), + this.client.guilds.cache.get(id).channels, + ]); + + guild.channels = channels; + + return res(guild); + } catch (err) { + return rej(err); + } + }); + + this.isLoading.set(id, p); + + setTimeout(() => { + if (!this.isLoading.has(id)) { return; } + this.isLoading.delete(id); + }, 10000); + + return p; + }; } \ No newline at end of file diff --git a/src/Managers/CommandManager.ts b/src/Managers/CommandManager.ts new file mode 100644 index 00000000..b21a8ceb --- /dev/null +++ b/src/Managers/CommandManager.ts @@ -0,0 +1,37 @@ +import { Client } from 'discord.js'; +import Naga from '../Core/Naga'; +import { EventCollection } from '../Structures'; + +export default class CommandManager extends EventCollection { + public naga: Naga; + public _client: Client; + + constructor(naga: Naga) { + super(); + this.naga = naga; + this._client = naga.client; + } + + public get logger() { + return this.naga.logger; + } + + /** + * Loads commands into the database + * @param Command Command class instance + * @returns + */ + public register(Command: any): void { + if (Object.getPrototypeOf(Command).name !== 'Command') { + return this.logger.debug('Skipping unknown command'); + } + + let command = new Command(this.naga); + + command.name = command.aliases[0]; + + this.logger.info(`Registering command ${command.name}`); + + this.naga.models.Command.updateMany({ name: command.name, _state: this.naga.config.state }, command.toJSON(), { upsert: true }) + } +} \ No newline at end of file diff --git a/src/Managers/GuildManager.ts b/src/Managers/GuildManager.ts new file mode 100644 index 00000000..fd437e1c --- /dev/null +++ b/src/Managers/GuildManager.ts @@ -0,0 +1,234 @@ +import { Client, Guild } from 'discord.js'; +import { Collection } from '../Structures'; +import Naga from '../'; + +export default class GuildManager extends Collection { + public naga: Naga.Naga; + public client: Client; + public config: Naga.NagaConfig; + public _activeThreshold: number; + public _registering: any; + + constructor(naga: any) { + super(); + this.naga = naga; + this.client = naga.client; + this.config = naga.config; + this._registering = new Set(); + this._activeThreshold = 3600 * 1000; // 24 hrs + } + + // Global config + get globalConfig() { + return this.naga.globalConfig; + } + + /** + * Update a guild's database entry + * @param id Guild ID + * @returns + */ + public guildUpdate(id: string) { + if (!this.client.guilds.cache.has(id)) return; + } + + public getOrFetch(id: string) { + const doc = this.get(id); + if (doc) { + doc.cachedAt = Date.now(); + return Promise.resolve(doc); + } + + return this.fetch(id).then((doc: any) => { + if (!doc) { + return this.registerGuild(this.client.guilds.cache.get(id)); + } + + doc.cachedAt = Date.now(); + this.set(doc._id, doc); + + return doc; + }); + } + + /** + * Fetches a guild from the database + * @param id Guild ID + * @returns + */ + public fetch(id: string) { + let updateKeys = ['name', 'iconURL', 'ownerID', 'memberCount']; + return new Promise((resolve, reject) => { + this.naga.models.Server.findAndPopulate(id).then((doc: any) => { + if (!doc) return resolve(); + doc = doc.toObject(); + let update: any = false; + + if (this.client.guilds.cache.has(id)) { + const guild = this.naga.guilds.cache.get(id); + + if (!doc.longId) { + update = update || {}; + update.longId = guild.id; + }; + + for (let key of updateKeys) { + if (guild[key] && doc[key] !== guild[key]) { + update = update || {}; + update[key] = guild[key]; + doc[key] = guild[key]; + } + } + + if (doc.deleted === true) { + update = update || {}; + update.deleted = false; + } + + if (!doc.lastActive || (Date.now() - doc.lastActive) > this._activeThreshold) { + update = update || {}; + update.lastActive = Date.now(); + this.setActive(guild, update.lastActive); + } + + if (update) { + this.update(id, { $set: update }).catch((err: Error) => this.naga.logger.error(err)); + } + } + this.set(doc._id, doc); + return resolve(doc); + }).catch((err: Error) => reject(err)); + }); + } + + setActive(guild: any, time: Date) { + guild.lastActive = time; + this.naga.redis.hset(`guild_activity:${this.config.client.id}:${this.config.client.options.maxShards}:${guild.shard.id}`, guild.id, time) + .catch((): any => null); + } + + /** + * Guild created event listener + * @param {Guild} guild Guild object + */ + async guildCreated(guild: Guild) { + this.naga.logger.info(`Connected to guild: ${guild.id} with ${guild.channels.cache.size} channels and ${guild.members.cache.size} members | ${guild.name}`); + + try { + var doc = await this.naga.models.Server.findOne({ _id: guild.id }).lean().exec(); + if (!doc) { + return this.registerGuild(guild); + } + + await this.update(guild.id, { $set: { deleted: false } }, { multi: true }); + this.set(doc._id, doc); + } catch (err) { + return this.naga.logger.error(err); + } + return false; + } + + /** + * Guild deleted event listener + * @param {Guild} guild Guild object + */ + async guildDeleted(guild: Guild) { + if (!guild.available) return; + + this.update(guild.id, { $set: { deleted: true, deletedAt: new Date() } }) + .catch((err: Error) => this.naga.logger.error(err)); + } + + /** + * Loads a guild into the database + * @param guild Guild object + * @returns + */ + public registerGuild(guild: Guild) { + console.log('hi') + if (!guild) return; + + let doc: any = { + _id: guild.id, + clientID: this.config.client.id, + name: guild.name, + iconURL: guild.iconURL, + ownerID: guild.ownerId, + memberCount: guild.memberCount, + modules: {}, + commands: {}, + lastActive: Date.now() + }; + + this.naga.logger.info(`Registering guild ${guild.name} (${guild.id})`); + + return new Promise((resolve, reject) => { + for (let module of this.naga.modules.values()) { + if (module.core && (module.hasOwnProperty('list') && module.list === false)) continue; + doc.modules[module.module] = module.enabled; + } + + for (let command of this.naga.commands.values()) { + if (command.permissions === 'admin') continue; + + if (this.naga.modules.find((c: Naga.Command) => c.module === command.group) && doc.modules[command.group] === false) { + doc.commands[command.name] = false; + continue; + } + + doc.commands[command.name] = (command.enabled || !command.disabled); + } + + // update method implementation + }) + } + + /** + * Update a guild + * @param id Guild ID + * @param update + * @param args Args + * @returns + */ + public update(id: string, update: any, ...args: any[]) { + if (update.$set) { + const serverlistColl = this.naga.db.collection('serverlist_store'); + let serverlistUpdate: any = false; + if (update.$set.iconURL) { + serverlistUpdate = serverlistUpdate || {}; + serverlistUpdate.iconURL = update.$set.iconURL; + } + + if (update.$set.deleted === true) { + serverlistUpdate = serverlistUpdate || {}; + serverlistUpdate.markedForDeletionAt = Date.now(); + } + + if (update.$set.name) { + serverlistUpdate = serverlistUpdate || {}; + serverlistUpdate.name = update.$set.name; + } + + if (update.$set.memberCount) { + serverlistUpdate = serverlistUpdate || {}; + serverlistUpdate.memberCount = update.$set.memberCount; + } + + if (serverlistUpdate) { + serverlistColl.update({ id }, { $set: serverlistUpdate }); + } + + if (update.$set.deleted === false) { + serverlistColl.update({ id }, { $unset: { markedForDeletionAt: 1 } }); + } + } + + try { + const result = this.naga.models.Server.update({ _id: id }, update, ...args); + this.naga.redis.publish('guildConfig', id); + return result; + } catch (err) { + this.naga.logger.error(err); + } + } +} \ No newline at end of file diff --git a/src/Managers/ModuleManager.ts b/src/Managers/ModuleManager.ts new file mode 100644 index 00000000..bcece47b --- /dev/null +++ b/src/Managers/ModuleManager.ts @@ -0,0 +1,65 @@ +import { Client } from 'discord.js'; +import Naga from '../Core/Naga'; +import { Collection, Command, Module } from '../Structures'; +const each = require('async-each'); + +import * as modules from '../Modules'; + +export default class ModuleManager extends Collection { + public naga: Naga; + public _client: Client; + public moduleList: Module[]; + + constructor(naga: Naga) { + super(); + this.naga = naga; + this._client = naga.client; + this.loadModules(); + } + + public get logger() { + return this.naga.logger; + } + + /** + * Loads all modules from the modules directory + */ + public loadModules(): void { + for (const Value of Object.values(modules)) { + const module = new Value(this.naga); + try { + this.register(module); + } catch (e) { + this.logger.error(e); + } + } + this.logger.info(`Registered ${Object.keys(modules).length} modules.`) + } + + /** + * Loads modules into the database. + * @param module Module class instance + * @returns + */ + public register(module: any): void { + if (!(module instanceof Module)) { + return this.logger.error(`${module} is an invalid module.`); + } + + //@ts-ignore + let activeModule = this.get(module.name); + + if (activeModule) { + // to-do + } + + this.logger.debug(`Registering module ${module.name}.`); + + if (module.commands) { + const commands = Array.isArray(module.commands) ? module.commands : Object.values(module.commands); + each(commands, (command: Command) => this.naga.commands.register(command)); + } + + module.ensureInterface(); + } +} \ No newline at end of file diff --git a/src/Managers/PermissionsManager.ts b/src/Managers/PermissionsManager.ts index 31fd3509..f3622849 100644 --- a/src/Managers/PermissionsManager.ts +++ b/src/Managers/PermissionsManager.ts @@ -15,6 +15,7 @@ export default class PermissionsManager { */ public isDeveloper(user: djs.User): boolean { if (!user) return false; + return true; } /** diff --git a/src/Modules/Info/commands/Test.ts b/src/Modules/Info/commands/Test.ts new file mode 100644 index 00000000..0ca64ffc --- /dev/null +++ b/src/Modules/Info/commands/Test.ts @@ -0,0 +1,23 @@ +import { Message } from 'discord.js'; + +import Command from '../../../Structures/Command'; +import CommandMetadata from '../../../Structures/CommandMetadata'; + +export class Test extends Command { + + public aliases : string[] = ['test']; + public description : string = 'Displays the ping between Naga and the Discord API'; + public usage : string = 'test'; + public example : string = 'test'; + public cooldown : number = 5000; + public expectedArgs : number = 0; + + public execute({ message, t}: CommandMetadata) { + let start = Date.now(); + + return this.sendMessage(message.channel, 'Pong!').then((msg: Message) => { + let diff = (Date.now() - start); + return msg.edit(`Pong! \`${diff}ms\``); + }) + } +} \ No newline at end of file diff --git a/src/Modules/Info/commands/index.ts b/src/Modules/Info/commands/index.ts new file mode 100644 index 00000000..8638d571 --- /dev/null +++ b/src/Modules/Info/commands/index.ts @@ -0,0 +1 @@ +export {Test} from './Test'; \ No newline at end of file diff --git a/src/Modules/Info/index.ts b/src/Modules/Info/index.ts new file mode 100644 index 00000000..f1820527 --- /dev/null +++ b/src/Modules/Info/index.ts @@ -0,0 +1,11 @@ +import Module from '../../Structures/Module'; +import * as commands from './commands'; + +export default class Info extends Module { + public module : string = 'Info'; + public description : string = 'Info commands'; + public enabled : boolean = true; + public commands : {} = commands; + + public start() {}; +} \ No newline at end of file diff --git a/src/Modules/Misc/commands/Ping.ts b/src/Modules/Misc/commands/Ping.ts index be74a685..047e3943 100644 --- a/src/Modules/Misc/commands/Ping.ts +++ b/src/Modules/Misc/commands/Ping.ts @@ -1,11 +1,12 @@ +'use-strict'; + import { Message } from 'discord.js'; import Command from '../../../Structures/Command'; import CommandMetadata from '../../../Structures/CommandMetadata'; -export default class Ping extends Command { - - public aliases : string[] = ['ping']; +export class Ping extends Command { + public aliases : string[] = ['ping']; public description : string = 'Displays the ping between Naga and the Discord API'; public usage : string = 'ping'; public example : string = 'ping'; diff --git a/src/Modules/Misc/commands/index.ts b/src/Modules/Misc/commands/index.ts index 8a4e32df..b0564f48 100644 --- a/src/Modules/Misc/commands/index.ts +++ b/src/Modules/Misc/commands/index.ts @@ -1 +1 @@ -export {default as Ping} from './Ping'; \ No newline at end of file +export {Ping} from './Ping'; \ No newline at end of file diff --git a/src/Modules/Misc/index.ts b/src/Modules/Misc/index.ts index bdefe5c9..8fe4d94e 100644 --- a/src/Modules/Misc/index.ts +++ b/src/Modules/Misc/index.ts @@ -2,7 +2,7 @@ import Module from '../../Structures/Module'; import * as commands from './commands'; export default class Misc extends Module { - public module : string = 'Misc'; + public module : string = 'Misc'; public description : string = 'Miscellaneous commands'; public enabled : boolean = true; public commands : {} = commands; diff --git a/src/Modules/index.ts b/src/Modules/index.ts index cdf00f48..48b328c6 100644 --- a/src/Modules/index.ts +++ b/src/Modules/index.ts @@ -1 +1,2 @@ -export {default as Misc} from './Misc'; \ No newline at end of file +export { default as Info } from './Info'; +export { default as Misc } from './Misc'; \ No newline at end of file diff --git a/src/Structures/Base.ts b/src/Structures/Base.ts index ac1b2357..c65a10cb 100644 --- a/src/Structures/Base.ts +++ b/src/Structures/Base.ts @@ -6,7 +6,7 @@ export default class Base { public guild!: djs.Guild; private _naga: Naga; - constructor(naga: Naga, guild: djs.Guild) { + constructor(naga: Naga, guild?: djs.Guild) { this._naga = naga; if (guild) { @@ -14,7 +14,6 @@ export default class Base { } } - /** * The Naga instance */ @@ -33,6 +32,30 @@ export default class Base { return this.naga.utils; } + public toJSON(): object { + const copy: {[key: string]: any} = {}; + + for (const key in this) { + if (!this.hasOwnProperty(key) || key.startsWith('_')) { + continue; + } + + if (!this[key]) { + copy[key] = this[key]; + } else if (this[key] instanceof Set) { + copy[key] = Array.from(this[key]); + } else if (this[key] instanceof Map) { + copy[key] = Array.from((this[key]).values()); + } else if ((this[key]).toJSON != undefined) { + copy[key] = (this[key]).toJSON(); + } else { + copy[key] = this[key]; + } + } + + return copy; + } + /** * Checks if user is a developer * @param user A User object diff --git a/src/Structures/Collection.ts b/src/Structures/Collection.ts new file mode 100644 index 00000000..ea47622d --- /dev/null +++ b/src/Structures/Collection.ts @@ -0,0 +1,41 @@ +/** + * Generic collection class that extends Map and provides array methods + * @abstract Collection + * @extends Map + */ + export default class Collection extends Map { + /** + * Convert Map to array + */ + public toArray() { + return [...this.values()]; + } + + /** + * Filter values by function + */ + public filter(callbackFn: any, thisArg?: any) { + return this.toArray().filter(callbackFn, thisArg); + } + + /** + * Map values by function + */ + public map(callbackFn: any, thisArg?: any) { + return this.toArray().map(callbackFn, thisArg); + } + + /** + * Reduce values by function + */ + public reduce(callbackFn: any, currentIndex: number) { + return this.toArray().reduce(callbackFn, currentIndex); + } + + /** + * Find values by function + */ + public find(predicate: any, thisArg: any) { + return this.toArray().find(predicate, thisArg); + } +} diff --git a/src/Structures/Command.ts b/src/Structures/Command.ts index 62567831..88f41b76 100644 --- a/src/Structures/Command.ts +++ b/src/Structures/Command.ts @@ -6,6 +6,7 @@ interface Command { cooldown?: number; defaultCommand?: string; disableDM?: boolean; + [key: string]: any; } /** @@ -21,10 +22,34 @@ abstract class Command extends Base { public abstract example: string|string[]; public abstract expectedArgs: number; - public abstract execute(e: any): Promise; + public abstract execute(e: CommandMetadata): Promise; + + public ensureInterface() { + // required properties + if (this.aliases == undefined) { + throw new Error(`${this.constructor.name} command must define aliases property.`); + } + if (this.description == undefined) { + throw new Error(`${this.constructor.name} command must define description property.`); + } + if (this.usage == undefined) { + throw new Error(`${this.constructor.name} command must define usage property.`); + } + + // required methods + if (this.execute == undefined) { + throw new Error(`${this.constructor.name} command must have an execute method.`); + } + + // warnings + if (this.expectedArgs == undefined) { + this.logger.warn(`${this.constructor.name} should defined the expectedArgs property.`); + } + this.expectedArgs = this.expectedArgs || 0; + } public help(message: djs.Message): Promise { - const msg = []; + const msg: string[] = []; const command: any = this; const name = this.name; const description = `**Description:**` @@ -54,7 +79,7 @@ abstract class Command extends Base { public _execute(e: CommandMetadata): Promise { const { message, args, command } = e; - if ((this.expectedArgs && args.length < this.expectedArgs) || (args && args[0] == 'help')) { + if ((this.expectedArgs && args!.length < this.expectedArgs) || (args && args[0] == 'help')) { return this.help(message); } diff --git a/src/Structures/EventCollection.ts b/src/Structures/EventCollection.ts new file mode 100644 index 00000000..3f2c2682 --- /dev/null +++ b/src/Structures/EventCollection.ts @@ -0,0 +1,69 @@ +//@ts-nocheck + +import { EventEmitter } from 'events'; +import Collection from './Collection'; + +const collection = Symbol('collection'); + +export default class EventCollection extends EventEmitter { + constructor() { + super(); + this[collection] = new Collection(); + } + + get size() { + return this[collection].size; + } + + get length() { + return this[collection].size; + } + + public has(key: any) { + return this[collection].has(key); + } + + public get(key: any) { + return this[collection].get(key); + } + + public set(key: any, value: any) { + return this[collection].set(key, value); + } + + public delete(key: any) { + return this[collection].delete(key); + } + + public keys() { + return this[collection].keys(); + } + + public values() { + return this[collection].values(); + } + + public entries() { + return this[collection].entries(); + } + + public forEach(...args: any[]) { + return this[collection].forEach(...args); + } + + public filter(...args: any[]) { + return this[collection].filter(...args); + } + + public map(...args: any[]) { + return this[collection].map(...args); + } + + public reduce(...args: any[]) { + return this[collection].reduce(...args); + } + + public find(...args: any[]) { + return this[collection].find(...args); + } +} diff --git a/src/Structures/ICommand.ts b/src/Structures/ICommand.ts new file mode 100644 index 00000000..dc71a7e1 --- /dev/null +++ b/src/Structures/ICommand.ts @@ -0,0 +1,14 @@ +import CommandMetadata from './CommandMetadata'; + +export default interface CommandInterface { + group : string; + module? : string; + aliases : string[]; + description : string; + expectedArgs : number; + cooldown : number; + usage : string|string[]; + defaultUsage? : string; + disableDM? : boolean; + execute(data: CommandMetadata): Promise<{}>; +} diff --git a/src/Structures/Module.ts b/src/Structures/Module.ts index 42727692..2aeb3565 100644 --- a/src/Structures/Module.ts +++ b/src/Structures/Module.ts @@ -1,5 +1,4 @@ import * as djs from 'discord.js'; -import * as naga from '../Core/Naga'; import Base from './Base'; interface Module { @@ -16,6 +15,17 @@ abstract class Module extends Base { public abstract start(client: djs.Client, ...args: any[]): any; + public ensureInterface() { + // required properties + if (this.module == undefined) { + throw new Error(`${this.constructor.name} command must define module property.`); + } + if (this.enabled == undefined) { + throw new Error(`${this.constructor.name} command must define enabled property.`); + } + } + + public _start(client: djs.Client, ...args: any[]): any { if (this.start) { this.start(client, ...args); diff --git a/src/Structures/Utils.ts b/src/Structures/Utils.ts index df60f774..26d2f36a 100644 --- a/src/Structures/Utils.ts +++ b/src/Structures/Utils.ts @@ -1,20 +1,12 @@ import * as djs from 'discord.js'; /** - * Utility functions, from the Utils class of @dyno.gg/dyno-core. - * @license Creative-Commons-Attribution-NonCommercial-NoDerivs-4.0 - * @author Dyno Developers + * Utility functions, based on @dyno.gg/dyno-core */ export default class Utils { - private channelRegex: RegExp = new RegExp('{#([a-zA-Z0-9-_]+)}', 'g'); - private roleRegex: RegExp = new RegExp('{&([^}]+)}', 'g'); - private userRegex: RegExp = new RegExp('{@([^}]+)}', 'g'); - private emojiRegex: RegExp = new RegExp(/<(a)?:(\w+):(\d+)>/, 'g'); private cleanRegex: RegExp = new RegExp('([_\*`])', 'g'); - private lastMessage: number; - - constructor() {} + private lastMessage: number; /** * Escapes special characters from regex @@ -29,18 +21,16 @@ export default class Utils { return str.replace(this.cleanRegex, '\\$&'); } - public fullName(user: djs.User): string { + public fullName(user: djs.User|djs.ClientUser): string { user = user; let username = user.username; let discrim = user.discriminator; if (!username) return user.id; - username = this.clean(username); if (discrim === '0') return username; - return `${username}#${discrim}`; } @@ -112,6 +102,4 @@ export default class Utils { public hexToInt(color: string): number { return color.startsWith('#') ? parseInt(color.replace('#', ''), 16) : parseInt(color, 16); } -} - -export const utils = new Utils(); \ No newline at end of file +}; \ No newline at end of file diff --git a/src/Structures/index.ts b/src/Structures/index.ts new file mode 100644 index 00000000..8a44e120 --- /dev/null +++ b/src/Structures/index.ts @@ -0,0 +1,7 @@ +export { default as Base } from './Base'; +export { default as Collection } from './Collection'; +export { default as Command } from './Command'; +export { default as CommandMetadata } from './CommandMetadata'; +export { default as EventCollection } from './EventCollection'; +export { default as Module } from './Module'; +export { default as Utils } from './Utils'; \ No newline at end of file diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 00000000..1bfd3daf --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,141 @@ +import * as djs from 'discord.js'; + +declare module Naga { + export interface NagaConfig { + name: string; + author: string; + version: string|number; + state: number; + stateName: string; + prefix: string; + adminPrefix: string; + developers: string[]; + contributors: string[]; + pkg: any; + avatarGuild: string; + testGuilds: string[]; + client: ClientConfig; + database: { + uri: string; + }; + emojis: { + success: string; + error: string; + info: string; + }; + status: object; + } + + export interface ClientConfig { + id: string; + token: string; + options: { + maxShards: number; + allowedMentions: djs.MessageMentionOptions; + fetchAllUsers: boolean; + intents: djs.IntentsBitField; + messageCacheSize: number; + } + } + + export interface GlobalConfig { + prefix: string; + commands: {[key: string]: boolean}; + modules: {[key: string]: boolean}; + ignoredUsers: string[]; + [key: string]: any; + } + + export interface GuildConfig { + id: string; + name: string; + owner: string; + prefix: string; + modules: {[key: string]: boolean}; + commands: {[key: string]: boolean}; + adminRoles?: string[]; + modRoles?: string[]; + test?: boolean; + ignoredUsers?: string[]; + ignoredRoles?: string[]; + [key: string]: any; + } + + export class Naga { + public isReady: boolean; + public client: djs.Client; + public readonly config: NagaConfig; + public readonly globalconfig: GlobalConfig; + public readonly utils: Utils; + [key: string]: any; + } + + export class Base { + public readonly client: djs.Client; + public readonly naga: Naga; + public readonly config: NagaConfig; + public readonly globalconfig: GlobalConfig; + public readonly utils: Utils; + constructor(naga: Naga, guild: djs.Guild); + public isDeveloper(user: djs.User): boolean; + public isServerAdmin(member: djs.GuildMember, channel: djs.GuildChannel): boolean; + public isServerMod(member: djs.GuildMember): boolean; + public sendMessage(channel: djs.TextBasedChannel, content: any, options?: any): Promise; + } + + export class Module extends Base { + constructor(naga: Naga); + [key: string]: any; + } + + export interface ICommand { + group : string; + module? : string; + aliases : string[]; + description : string; + expectedArgs : number; + cooldown : number; + usage : string|string[]; + defaultUsage? : string; + disableDM? : boolean; + execute(data: CommandMetadata): Promise<{}>; + } + + export interface CommandMetadata { + message: djs.Message; + args?: any[]; + t?: Function; + command?: string; + guildConfig?: GuildConfig; + isDev?: boolean; + isAdmin?: boolean; + } + + export class Command extends Base { + public name: string; + public aliases: string[]; + constructor(naga: Naga); + public help(message: djs.Message, guildConfig: GuildConfig): Promise; + [key: string]: any; + } + + export class Utils { + public regEscape(str: string): string; + public clean(str: string); + public fullName(user: djs.User): string; + public async sendMessage(channel: djs.TextBasedChannel, message: any, options: any = {}): Promise; + public sendCode(channel: djs.TextBasedChannel, message: string = ' ', lang: string = '', options: any = {}): Promise; + public sortRoles(roles: any): djs.Role[]; + public hexToInt(color: string): number; + } + + export class PermissionsManager { + constructor(naga: Naga); + public naga: Naga; + public isDeveloper(user: djs.User): boolean; + public isServerAdmin(member: djs.GuildMember, channel: djs.GuildChannel): boolean; + public isServerMod(member: djs.GuildMember): boolean; + } +} + +export = Naga; \ No newline at end of file diff --git a/src/logo.ts b/src/logo.ts new file mode 100644 index 00000000..7dae02ae --- /dev/null +++ b/src/logo.ts @@ -0,0 +1,8 @@ +export = [ +' _ ____ _____ ____ ', +' / \\ /|/ _ \\/ __// _ \\ ', +' | |\\ ||| / \\|| | _| / \\| ', +' | | \\||| |-||| |_//| |-|| ', +' \\_/ \\|\\_/ \\|\\____/\\_/ \\| ', +' ', +]; \ No newline at end of file diff --git a/src/start.ts b/src/start.ts index da77bdfd..5b7c510e 100644 --- a/src/start.ts +++ b/src/start.ts @@ -1,11 +1,48 @@ import Naga from './Core/Naga'; +import { readFileSync } from 'fs'; +import util from 'util'; +import { exec } from 'child_process'; +import config from './Core/Config'; -const config: any = { - client: { - intents: process.env.INTENTS +const naga = new Naga(); + +let logo = require('./logo'); +logo = logo.join('\n'); + +function log(...args: any[]) { + process.stdout.write(`${util.format.apply(null, args)}\n`); +} + +async function init() { + log(logo); + log(`${config.name} ${config.stateName} ${config.pkg.version}`, '\n'); + + if (config.stateName === 'Development') { + try { + log(`Recent Git commits:`); + await gitInfo(); + } catch (err) { + console.error(err); + } } + naga.setup(naga.config.client.options); } -const naga = new Naga(); +function gitInfo(): Promise { + return new Promise((res, rej) => + exec('git log -n 3 --no-color --pretty=format:\'[ "%h", "%s", "%cr", "%an" ],\'', (err, stdout) => { + if (err) { + return rej(err); + } + + let str = stdout.split('\n').join(''); + str = str.substr(0, str.length - 1); + + let lines = JSON.parse(`[${str}]`); + lines = lines.map((l: any) => `[${l[0]}] ${l[1]} - ${l[2]}`); + log(`${lines.join('\n')}\n`); + return res(); + })); +} -naga.setup(config.client); \ No newline at end of file +init(); diff --git a/tsconfig.json b/tsconfig.json index 1412e50c..b3119dc6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,12 +5,15 @@ /* Modules */ "module": "commonjs", /* Specify what module code is generated. */ "rootDir": "./src", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + "types": [ + "discord.js", + "node" + ], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ @@ -27,19 +30,19 @@ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ - "outDir": "./build", /* Specify an output folder for all emitted files. */ + "outDir": "./build", /* Specify an output folder for all emitted files. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - "strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */ + "strict": false, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + "strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ @@ -56,5 +59,6 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + }, + // "files": ["src/index.d.ts"] }