diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 5dd55cb..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,201 +0,0 @@ -module.exports = { - 'env': { - 'browser': false, - 'commonjs': false, - 'es6': true, - 'node': true, - }, - 'extends': ['eslint:recommended'], - 'parser': '@typescript-eslint/parser', - 'parserOptions': { 'ecmaVersion': 12 }, - 'root': true, - 'rules': { - '@typescript-eslint/no-var-requires': ['off'], - 'array-bracket-newline': [ - 'error', - 'consistent' - ], - 'array-bracket-spacing': [ - 'error', - 'never' - ], - 'array-element-newline': [ - 'error', - 'consistent' - ], - 'arrow-body-style': [ - 'error', - 'as-needed' - ], - 'arrow-parens': [ - 'error', - 'as-needed' - ], - 'block-spacing': [ - 'error', - 'always' - ], - 'brace-style': [ - 'error', - '1tbs' - ], - 'comma-dangle': [ - 'error', - { - 'arrays': 'never', - 'exports': 'never', - 'functions': 'never', - 'imports': 'never', - 'objects': 'always-multiline', - } - ], - 'comma-spacing': [ - 'error', - { - 'after': true, - 'before': false, - } - ], - 'comma-style': [ - 'error', - 'last' - ], - 'computed-property-spacing': [ - 'error', - 'never' - ], - 'curly': [ - 'error', - 'multi-line', // 'multi' - 'consistent' - ], - 'default-case-last': [ - 'error' - ], - 'dot-location': [ - 'error', - 'property' - ], - 'dot-notation': [ - 'error' - ], - 'eqeqeq': [ - 'error' - ], - 'func-call-spacing': [ - 'error', - 'never' - ], - 'indent': [ - 'error', - 'tab' - ], - 'linebreak-style': [ - 'off', - 'windows' - ], - 'max-depth': [ - 'warn', - { 'max': 5 } - ], - 'max-len': [ - 'warn', - { - 'code': 150, - 'ignoreRegExpLiterals': true, - 'ignoreStrings': true, - 'ignoreTemplateLiterals': true, - 'ignoreTrailingComments': true, - 'ignoreUrls': true, - } - ], - 'max-lines': [ - 'warn' - ], - 'max-statements-per-line': [ - 'error' - ], - 'multiline-comment-style': [ - 'warn' - ], - 'no-console': [ - 'off' - ], - 'no-return-assign': [ - 'error' - ], - 'no-template-curly-in-string': [ - 'warn' - ], - 'no-trailing-spaces': [ - 'error' - ], - 'no-underscore-dangle': [ - 'error', { - 'allowAfterThis': true, - 'allowFunctionParams': true, - } - ], - 'no-unneeded-ternary': [ - 'error' - ], - 'no-var': [ - 'error' - ], - 'no-whitespace-before-property': [ - 'error' - ], - 'object-curly-newline': [ - 'error', - { - 'minProperties': 2, - 'multiline': true, - } - ], - 'object-curly-spacing': [ - 'error', - 'always' - ], - 'object-property-newline': [ - 'error' - ], - 'operator-linebreak': [ - 'error' - ], - 'prefer-arrow-callback': [ - 'error' - ], - 'prefer-const': [ - 'error', - { - 'destructuring': 'all', - 'ignoreReadBeforeAssign': false, - } - ], - 'quotes': [ - 'error', - 'single' - ], - 'rest-spread-spacing': [ - 'error', - 'never' - ], - 'semi': [ - 'error', - 'always' - ], - 'sort-keys': [ - 'error', - 'asc', - { 'natural': true } - ], - 'space-in-parens': [ - 'error', - 'never' - ], - 'spaced-comment': [ - 'error', - 'always' - ], - }, -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..6276302 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,209 @@ +{ + "env": { + "browser": false, + "commonjs": false, + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended" + ], + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "root": true, + "rules": { + "array-bracket-newline": [ + "error", + "consistent" + ], + "array-bracket-spacing": [ + "error", + "never" + ], + "array-element-newline": [ + "error", + "consistent" + ], + "arrow-body-style": [ + "error", + "as-needed" + ], + "arrow-parens": [ + "error", + "as-needed" + ], + "block-spacing": [ + "error", + "always" + ], + "brace-style": [ + "error", + "1tbs" + ], + "comma-dangle": [ + "error", + { + "arrays": "never", + "exports": "never", + "functions": "never", + "imports": "never", + "objects": "always-multiline" + } + ], + "comma-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "comma-style": [ + "error", + "last" + ], + "computed-property-spacing": [ + "error", + "never" + ], + "curly": [ + "error", + "multi-line", // "multi" + "consistent" + ], + "default-case-last": [ + "error" + ], + "dot-location": [ + "error", + "property" + ], + "dot-notation": [ + "error" + ], + "eqeqeq": [ + "error" + ], + "func-call-spacing": [ + "error", + "never" + ], + "indent": [ + "error", + "tab" + ], + "linebreak-style": [ + "off", + "windows" + ], + "max-depth": [ + "warn", + { + "max": 5 + } + ], + "max-len": [ + "warn", + { + "code": 150, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true, + "ignoreTrailingComments": true, + "ignoreUrls": true + } + ], + "max-lines": [ + "warn" + ], + "max-statements-per-line": [ + "error" + ], + "multiline-comment-style": [ + "warn" + ], + "no-console": [ + "off" + ], + "no-return-assign": [ + "error" + ], + "no-template-curly-in-string": [ + "warn" + ], + "no-trailing-spaces": [ + "error" + ], + "no-underscore-dangle": [ + "error", + { + "allowAfterThis": true, + "allowFunctionParams": true + } + ], + "no-unneeded-ternary": [ + "error" + ], + "no-var": [ + "error" + ], + "no-whitespace-before-property": [ + "error" + ], + "object-curly-newline": [ + "error", + { + "minProperties": 2, + "multiline": true + } + ], + "object-curly-spacing": [ + "error", + "always" + ], + "object-property-newline": [ + "error" + ], + "operator-linebreak": [ + "error" + ], + "prefer-arrow-callback": [ + "error" + ], + "prefer-const": [ + "error", + { + "destructuring": "all", + "ignoreReadBeforeAssign": false + } + ], + "quotes": [ + "error", + "single" + ], + "rest-spread-spacing": [ + "error", + "never" + ], + "semi": [ + "error", + "always" + ], + "sort-keys": [ + "error", + "asc", + { + "natural": true + } + ], + "space-in-parens": [ + "error", + "never" + ], + "spaced-comment": [ + "error", + "always" + ] + } +} \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..40bf6bd --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "Node", + "baseUrl": "src", + "resolveJsonModule": true, + "checkJs": true, + "paths": { + "@/*": ["*.mjs"], + "#/*": ["*.json"] + } + }, + "include": [ + "src/**/*.mjs", + "src/**/*.js" + ] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..49c37ba --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "discord-tickets", + "version": "4.0.0", + "private": "true", + "description": "An open-source Discord bot for ticket management", + "main": "src/index.mjs", + "module": "src/index.mjs", + "type": "module", + "scripts": { + "contributors:add": "all-contributors add", + "contributors:generate": "all-contributors generate", + "keygen": "node scripts/keygen.mjs", + "lint": "eslint src scripts --ext mjs --fix", + "test": "echo \"There's nothing to test\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/discord-tickets/bot.git" + }, + "keywords": [ + "discord", + "tickets", + "bot" + ], + "author": "eartharoid", + "license": "GPL-3.0", + "bugs": { + "url": "https://github.com/discord-tickets/bot/issues" + }, + "homepage": "https://discordtickets.app", + "engines": { + "node": ">=16.6" + }, + "dependencies": { + "@eartharoid/dtf": "^2.0.1", + "@sapphire/framework": "^2.4.1", + "discord.js": "^13.6.0", + "dotenv-cra": "^3.0.2", + "leeks.js": "^0.2.4", + "leekslazylogger": "^4.1.7", + "semver": "^7.3.5", + "terminal-link": "^3.0.0", + "yaml": "^1.10.2" + }, + "devDependencies": { + "all-contributors-cli": "^6.20.0", + "eslint": "^8.11.0" + } +} \ No newline at end of file diff --git a/scripts/keygen.mjs b/scripts/keygen.mjs new file mode 100644 index 0000000..614b775 --- /dev/null +++ b/scripts/keygen.mjs @@ -0,0 +1,8 @@ +import { randomBytes } from 'crypto'; +import { short } from 'leeks.js'; + +console.log(short( + 'Set the "DB_ENCRYPTION_KEY" environment variable to: \n&1&!f' + + randomBytes(24).toString('hex') + + '&r\n\n&0&!e WARNING &r &e&lDo not lose the encryption key or most of the data in the database will be unreadable, requiring a new key and a full reset.' +)); \ No newline at end of file diff --git a/src/index.mjs b/src/index.mjs new file mode 100644 index 0000000..ebb2379 --- /dev/null +++ b/src/index.mjs @@ -0,0 +1,75 @@ +/** + * Discord Tickets + * Copyright (C) 2022 Isaac Saunders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @name discord-tickets/bot + * @description An open-source Discord bot for ticket management + * @copyright 2022 Isaac Saunders + * @license GNU-GPLv3 + */ + +import dotenv from 'dotenv-cra'; +import fs from 'fs'; +import semver from 'semver'; +import { colours } from 'leeks.js'; +import logger from './lib/logger.mjs'; +import banner from './lib/banner.mjs'; +import YAML from 'yaml'; + +process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set +dotenv.config(); // load env file + +const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + +// check node version +if (!semver.satisfies(process.versions.node, pkg.engines.node)) { + console.log('\x07' + colours.redBright(`Error: Discord Tickets requires Node.js version ${pkg.engines.node}; you are currently using ${process.versions.node}`)); + process.exit(1); +} + +if (process.env.DB_ENCRYPTION_KEY === undefined) { + console.log('\x07' + colours.redBright('Error: The "DB_ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key, or set it to "false" to disable encryption (not recommended).')); + process.exit(1); +} + +console.log(banner(pkg.version)); // print big title + +process.env.CONFIG_PATH ??= './user/config.yml'; // set default config file path + +if (!fs.existsSync(process.env.CONFIG_PATH)) { + const examplePath = './user/example.config.yml'; + if (!fs.existsSync(examplePath)) { + console.log('\x07' + colours.redBright('The config file does not exist, and the example file is missing so cannot be copied from.')); + process.exit(1); + } else { + console.log('Creating config file...'); + fs.copyFileSync(examplePath, process.env.CONFIG_PATH); + console.log(`Copied config to ${process.env.CONFIG_PATH}`); + } +} + +const config = YAML.parse(fs.readFileSync(process.env.CONFIG_PATH, 'utf8')); + +const log = logger(config); + + +process.on('unhandledRejection', error => { + log.notice(`Discord Tickets v${pkg.version} on Node.js v${process.versions.node} (${process.platform})`); + log.notice('An error was not caught'); + if (error instanceof Error) log.warn(`Uncaught ${error.name}`); + log.error(error); +}); + diff --git a/src/lib/banner.mjs b/src/lib/banner.mjs new file mode 100644 index 0000000..811e9ed --- /dev/null +++ b/src/lib/banner.mjs @@ -0,0 +1,22 @@ +import { colours } from 'leeks.js'; +import link from 'terminal-link'; + +export default version => colours.cyan(` +######## #### ###### ###### ####### ######## ######## +## ## ## ## ## ## ## ## ## ## ## ## ## +## ## ## ## ## ## ## ## ## ## ## +## ## ## ###### ## ## ## ######## ## ## +## ## ## ## ## ## ## ## ## ## ## +## ## ## ## ## ## ## ## ## ## ## ## ## +######## #### ###### ###### ####### ## ## ######## +######## #### ###### ## ## ######## ######## ###### + ## ## ## ## ## ## ## ## ## ## + ## ## ## ## ## ## ## ## + ## ## ## ##### ###### ## ###### + ## ## ## ## ## ## ## ## + ## ## ## ## ## ## ## ## ## ## + ## #### ###### ## ## ######## ## ###### +`) + +colours.cyanBright(`\n${link('Discord Tickets', 'https://discordtickets.app')} bot v${version} by eartharoid`) + +colours.cyanBright('\n' + link('Sponsor this project', 'https://discordtickets.app/sponsor')) + +'\n\n'; \ No newline at end of file diff --git a/src/lib/constants.js b/src/lib/constants.js new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/logger.mjs b/src/lib/logger.mjs new file mode 100644 index 0000000..6372ff3 --- /dev/null +++ b/src/lib/logger.mjs @@ -0,0 +1,44 @@ +import Logger from 'leekslazylogger'; +import DTF from '@eartharoid/dtf'; +import { short } from 'leeks.js'; + +const dtf = new DTF(); +const colours = { + critical: ['&!4&f', '&!4&f'], + debug: ['&1', '&9'], + error: ['&4', '&c'], + info: ['&3', '&b'], + notice: ['&!6&0', '&!6&0'], + success: ['&2', '&a'], + warn: ['&6', '&e'], +}; + +export default config => { + const transports = [ + new Logger.transports.ConsoleTransport({ + format: log => { + const timestamp = dtf.fill('DD/MM/YY HH:mm:ss', log.timestamp); + const colour = colours[log.level.name]; + return short(`&f&!7${timestamp}&r ${colour[0]}[${log.level.name.toUpperCase()}]&r ${log.namespace ? `&d(${log.namespace.toUpperCase()})&r ` : ''}${colour[1]}${log.content}`); + }, + level: config.logs.level, + }) + ]; + + if (config.logs.files.enabled) { + transports.push( + new Logger.transports.FileTransport({ + clean_directory: config.logs.files.keepFor, + directory: config.logs.files.directory, + level: config.logs.level, + name: 'Discord Tickets by eartharoid', + }) + ); + } + + return new Logger({ + namespaces: ['commands', 'http', 'listeners'], + transports, + }); +}; + diff --git a/user/example.config.yml b/user/example.config.yml index 8e614f8..d443b6e 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -16,7 +16,7 @@ logs: files: + directory: ./logs enabled: true keepFor: 30 # days - split: true # split stdout (info) and stderr (warnings & errors) into separate files? level: info \ No newline at end of file