diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 524ff9c..885a05d 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -2,7 +2,7 @@ name: Generate Changelog on: schedule: - - cron: "0 3 * * 1" + - cron: "0 3 * * *" workflow_dispatch: jobs: @@ -37,4 +37,4 @@ jobs: git diff --cached --quiet && exit 0 git commit -m "Update changelog" - git push origin main + git push origin changelog diff --git a/.gitignore b/.gitignore index 9860f0c..aadd713 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ +# Our +env.sh + # Build extra dist + # Logs logs *.log diff --git a/.oxfmtrc.json b/.oxfmtrc.json index eabeb37..f63218e 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -12,5 +12,6 @@ "jsxSingleQuote": false, "quoteProps": "as-needed", - "embeddedLanguageFormatting": "auto" + "embeddedLanguageFormatting": "auto", + "endOfLine": "lf" } diff --git a/.swcrc b/.swcrc index 296cd72..6497f33 100644 --- a/.swcrc +++ b/.swcrc @@ -13,5 +13,5 @@ "type": "commonjs" }, "sourceMaps": false, - "minify": true + "minify": false } diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index f11240c..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,34 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## Unreleased - -### Miscellaneous Tasks - -- Weekly update by github-actions[bot] (8e7f6b8) -- Update checkVersionExists script by vrdons (73eef8b) - -## [0.1.0-alpha.1] - 2026-01-15 - -### Bug Fixes - -- Npm build script by vrdons (6f235e3) - -### Features - -- Github Release by vrdons (80f4b1d) - -### Miscellaneous Tasks - -- Base template by vrdons (473e8a6) -- Dont ignore package-lock.json by vrdons (f6d2e9e) -- Add Error handling by vrdons (0525018) - -### Ci - -- Fix checking code by vrdons (310c4e1) -- Fix releasing github by vrdons (6b70d45) -- Move github-release to check by vrdons (9565461) - - diff --git a/examples/basic_client/events/ready.js b/examples/basic_client/events/ready.js new file mode 100644 index 0000000..78b2120 --- /dev/null +++ b/examples/basic_client/events/ready.js @@ -0,0 +1,7 @@ +const { EventBuilder } = require("../../../dist"); + +new EventBuilder("clientReady", false, (context) => { + context.logger.log("Client connected!"); + context.logger.warn(`Current user: ${context.client.user.username}`); + context.logger.warn(`Current prefix: ${context.client.prefix ?? "none"}`); +}); diff --git a/examples/basic_client/index.js b/examples/basic_client/index.js new file mode 100644 index 0000000..f62745f --- /dev/null +++ b/examples/basic_client/index.js @@ -0,0 +1,34 @@ +const arox = require("../../dist/index"); + +const client = new arox.Client({ + intents: 37376, + prefix: { enabled: true, prefix: "a!" }, + logger: { + depth: 0, + }, + autoRegisterCommands: false, +}); + +arox.setClient(client); +const command = new arox.CommandBuilder({ + name: "arox", + description: "Arox test command", + slash: true, + prefix: true, +}); +arox.clearClient(); + +command + .onMessage(function (ctx) { + const { message } = ctx; + void message.reply("Çalışıyom ulan şurda rahat bırak beni"); + }) + .onInteraction(function (ctx) { + const { interaction } = ctx; + void interaction.reply("Çalışıyom ulan şurda rahat bırak beni"); + }); + +async function init() { + await client.login(process.env.BOT_TOKEN); +} +void init(); diff --git a/package-lock.json b/package-lock.json index ef734b5..d01e2cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,23 @@ { "name": "framework", - "version": "0.1.0-alpha.2", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "framework", - "version": "0.1.0-alpha.2", + "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "chalk": "^4.1.2" + "@sapphire/timestamp": "^1.0.5", + "@swc/helpers": "^0.5.18", + "colorette": "^2.0.20", + "fast-glob": "^3.3.3", + "lodash": "^4.17.21" }, "devDependencies": { "@swc/cli": "^0.7.10", + "@types/lodash": "^4.17.23", "@types/node": "^25.0.8", "husky": "^9.1.7", "libnpmpack": "^9.0.12", @@ -540,6 +545,41 @@ "node": ">= 10" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@npmcli/agent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", @@ -1235,6 +1275,16 @@ "npm": ">=7.0.0" } }, + "node_modules/@sapphire/timestamp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sapphire/timestamp/-/timestamp-1.0.5.tgz", + "integrity": "sha512-oNwWyNdbt5wm4aYZvlHl1+64U3g0xrFmRIHsnER7RgMxNnp/wmAE4yTK2oUHeadg3t4V9iYctPAQCF+aINke4g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@sigstore/bundle": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", @@ -1590,6 +1640,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@swc/helpers": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@swc/types": { "version": "0.1.25", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", @@ -1687,6 +1746,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", @@ -1894,21 +1960,6 @@ "node": ">= 14" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/arch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", @@ -2050,6 +2101,18 @@ "balanced-match": "^1.0.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -2137,22 +2200,6 @@ "node": ">=14.16" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2173,22 +2220,10 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "license": "MIT" }, "node_modules/commander": { @@ -2467,6 +2502,31 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2540,6 +2600,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-versions": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", @@ -2610,6 +2682,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/glob/node_modules/minimatch": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", @@ -2659,15 +2743,6 @@ "dev": true, "license": "ISC" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/hosted-git-info": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", @@ -2860,6 +2935,36 @@ "node": ">= 12" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -2981,8 +3086,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", @@ -3051,6 +3155,40 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -3757,6 +3895,26 @@ "node": ">=10" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -3813,6 +3971,39 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4142,22 +4333,10 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz", + "integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -4227,6 +4406,18 @@ "node": "^20.0.0 || >=22.0.0" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/token-types": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", @@ -4267,8 +4458,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tuf-js": { "version": "4.1.0", diff --git a/package.json b/package.json index 45aee4a..4160ea0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "framework", - "version": "0.1.0-alpha.2", + "version": "0.1.0", "description": "", "keywords": [ "discord.js", @@ -19,6 +19,9 @@ "type": "commonjs", "main": "dist/index.js", "types": "dist/index.d.ts", + "imports": { + "#types/*": "./types/*" + }, "scripts": { "prepare": "node scripts/prepareHusky.js", "test": "echo \"Error: no test specified\" && exit 1", @@ -31,10 +34,15 @@ "release:npm": "node scripts/actions/npm.js" }, "dependencies": { - "chalk": "^4.1.2" + "@sapphire/timestamp": "^1.0.5", + "@swc/helpers": "^0.5.18", + "colorette": "^2.0.20", + "fast-glob": "^3.3.3", + "lodash": "^4.17.21" }, "devDependencies": { "@swc/cli": "^0.7.10", + "@types/lodash": "^4.17.23", "@types/node": "^25.0.8", "husky": "^9.1.7", "libnpmpack": "^9.0.12", diff --git a/src/classes/base/BaseClient.ts b/src/classes/base/BaseClient.ts deleted file mode 100644 index fb93305..0000000 --- a/src/classes/base/BaseClient.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Client, ClientOptions } from "discord.js"; -import { LoggerInstance, LogLevel } from "./Logger"; - -export interface BaseOptions extends ClientOptions { - logLevel?: LogLevel; -} - -export default class BaseClient extends Client { - readonly logger: LoggerInstance; - - constructor(opts: BaseOptions) { - super(opts); - this.logger = new LoggerInstance(opts.logLevel ?? "log"); - } -} diff --git a/src/classes/base/Logger.ts b/src/classes/base/Logger.ts deleted file mode 100644 index 6dc78ed..0000000 --- a/src/classes/base/Logger.ts +++ /dev/null @@ -1,45 +0,0 @@ -import chalk from "chalk"; -export type LogLevel = "debug" | "log" | "warn" | "error"; - -const LOG_LEVEL_PRIORITY: Record = { - debug: 0, - log: 1, - warn: 2, - error: 3, -}; - -export class LoggerInstance { - private level: LogLevel; - - constructor(level: LogLevel = "log") { - this.level = level; - } - - private shouldLog(level: LogLevel): boolean { - return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.level]; - } - - private get timestamp(): string { - return chalk.gray(`[${new Date().toISOString()}]`); - } - - log(...args: any[]) { - if (!this.shouldLog("log")) return; - console.log(this.timestamp, chalk.bgBlue.bold("LOG"), ...args); - } - - warn(...args: any[]) { - if (!this.shouldLog("warn")) return; - console.warn(this.timestamp, chalk.bgYellow.bold("WARN"), ...args); - } - - error(...args: any[]) { - if (!this.shouldLog("error")) return; - console.error(this.timestamp, chalk.bgRed.bold("ERROR"), ...args); - } - - debug(...args: any[]) { - if (!this.shouldLog("debug")) return; - console.debug(this.timestamp, chalk.bgMagenta.bold("DEBUG"), ...args); - } -} diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..d4eaf63 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,12 @@ +import { Client } from "./structures/Client"; + +// Birden fazla client olursa hata çıkartabilir ama aklıma gelen tek şey bu +export let currentClient: Client | null = null; + +export function setClient(client: Client) { + currentClient = client; +} + +export function clearClient() { + currentClient = null; +} diff --git a/src/events/interaction.ts b/src/events/interaction.ts new file mode 100644 index 0000000..0f6832b --- /dev/null +++ b/src/events/interaction.ts @@ -0,0 +1,39 @@ +import { Events } from "discord.js"; +import { EventBuilder } from "../structures/Event"; +import { Context } from "../structures/Context"; + +new EventBuilder(Events.InteractionCreate, false).onExecute( + async function (context, interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = context.client.commands.get(interaction.commandName); + if (!command || !command.supportsSlash) { + await interaction.reply({ + content: "Command not found or disabled.", + ephemeral: true, + }); + return; + } + + try { + const ctx = new Context(context.client, { interaction }); + if (command._onInteraction) await command._onInteraction(ctx.toJSON()); + } catch (error) { + context.client.logger.error( + `Error executing command ${command.name}:`, + error + ); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } else { + await interaction.reply({ + content: "There was an error while executing this command!", + ephemeral: true, + }); + } + } + } +); diff --git a/src/events/message.ts b/src/events/message.ts new file mode 100644 index 0000000..7b92263 --- /dev/null +++ b/src/events/message.ts @@ -0,0 +1,51 @@ +import { Events } from "discord.js"; +import { EventBuilder } from "../structures/Event"; +import { Context } from "../structures/Context"; +import { deleteMessage } from "../utils/util"; + +new EventBuilder( + Events.MessageCreate, + false, + async function (context, message) { + if (message.author.bot) return; + const prefix = context.client.prefix; + if ( + typeof prefix !== "string" || + prefix.length === 0 || + !message.content.startsWith(prefix) + ) + return; + + const args = message.content.slice(prefix.length).trim().split(/ +/); + const commandName = args.shift()?.toLowerCase(); + if (!commandName) return; + + const commandAlias = context.client.aliases.findKey((cmd) => + cmd.has(commandName) + ); + + let command = context.client.commands.get(commandAlias ?? commandName); + if (!command || !command.supportsPrefix) { + await message + .reply({ + content: "Command not found or disabled.", + allowedMentions: { repliedUser: false }, + }) + .then(deleteMessage); + return; + } + + try { + const ctx = new Context(context.client, { message, args }); + context.logger.debug( + `${ctx.author?.tag ?? "Unknown"} used ${command.name}(message)` + ); + if (command._onMessage) await command._onMessage(ctx.toJSON()); + } catch (error) { + context.client.logger.error( + `Error executing command ${command.name}:`, + error + ); + } + } +); diff --git a/src/events/ready.ts b/src/events/ready.ts new file mode 100644 index 0000000..06317f5 --- /dev/null +++ b/src/events/ready.ts @@ -0,0 +1,8 @@ +import { Events } from "discord.js"; +import { EventBuilder } from "../structures/Event"; + +new EventBuilder(Events.ClientReady).onExecute(async function (context) { + if (context.client.options.autoRegisterCommands) { + await context.client.registerCommands(); + } +}); diff --git a/src/index.ts b/src/index.ts index 6097fa3..1e433b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,8 @@ +export * from "./structures/Client"; +export * from "./structures/Command"; +export * from "./structures/Context"; +export * from "./structures/Event"; +export * from "./structures/Argument"; +export * from "./utils/logger/Logger"; +export * from "./context"; export const version = "[VI]{{version}}[/VI]"; diff --git a/src/structures/Argument.ts b/src/structures/Argument.ts new file mode 100644 index 0000000..991aa20 --- /dev/null +++ b/src/structures/Argument.ts @@ -0,0 +1,36 @@ +import { + ApplicationCommandOptionData, + ApplicationCommandOptionType, +} from "discord.js"; + +export class Argument { + public readonly name: string; + public readonly description: string; + public readonly type: ApplicationCommandOptionType; + public readonly required: boolean; + public readonly choices?: { name: string; value: string | number }[]; + + constructor(data: { + name: string; + description: string; + type: ApplicationCommandOptionType; + required?: boolean; + choices?: { name: string; value: string | number }[]; + }) { + this.name = data.name; + this.description = data.description; + this.type = data.type; + this.required = data.required ?? false; + this.choices = data.choices; + } + + public toJSON(): ApplicationCommandOptionData { + return { + name: this.name, + description: this.description, + type: this.type, + required: this.required, + choices: this.choices, + } as ApplicationCommandOptionData; + } +} diff --git a/src/structures/Client.ts b/src/structures/Client.ts new file mode 100644 index 0000000..c7ef552 --- /dev/null +++ b/src/structures/Client.ts @@ -0,0 +1,119 @@ +import { + Client as DiscordClient, + Collection, + REST, + Routes, + IntentsBitField, +} from "discord.js"; +import { CommandBuilder } from "./Command"; +import path from "path"; +import { getFiles, getProjectRoot } from "../utils/Files"; +import { FrameworkOptions } from "#types/client.js"; +import { merge } from "lodash"; +import { clearClient, setClient } from "../context"; +import { getPrefix } from "../utils/util"; +import { Logger } from "../utils/logger/Logger"; + +const defaultOpts: Omit = { + paths: { + events: "events", + commands: "commands", + }, + autoRegisterCommands: true, +}; + +export class Client< + Ready extends boolean = boolean, +> extends DiscordClient { + public readonly logger: Logger; + public commands: Collection; + public aliases: Collection>; + public readonly prefix: string | false; + + declare public options: Omit & { + intents: IntentsBitField; + }; + + constructor(opts: FrameworkOptions) { + super(merge({}, defaultOpts, opts) as FrameworkOptions); + this.logger = new Logger(opts.logger); + this.commands = new Collection(); + this.aliases = new Collection(); + this.prefix = getPrefix(this.options.prefix ?? { enabled: false }); + + if (this.options.paths?.events) { + this.loadFiles( + path.join(getProjectRoot(), this.options.paths?.events) + ).catch((error) => this.logger.error("Error loading events:", error)); + } + + setClient(this); + try { + require("../events/ready"); + require("../events/interaction"); + if (this.prefix) require("../events/message"); + } finally { + clearClient(); + } + } + + async loadFiles(dir: string) { + if (!require("fs").existsSync(dir)) { + this.logger.warn(`Directory not found: ${dir}`); + return; + } + const files = getFiles(dir); + for (const file of files) { + await this.loadFile(file); + } + } + + async loadFile(file: string) { + try { + delete require.cache[require.resolve(file)]; + setClient(this); + require(file); + } catch (error) { + this.logger.error(`Error loading file ${file}:`, error); + } finally { + clearClient(); + } + } + + public async registerCommands() { + if (!this.token) { + this.logger.warn("registerCommands skipped: client token is not set."); + return; + } + if (!this.application) { + this.logger.warn( + "registerCommands skipped: client application is not ready." + ); + return; + } + + const slashCommands = this.commands + .filter((cmd) => cmd.supportsSlash) + .map((cmd) => ({ + name: cmd.name, + description: cmd.description, + options: cmd.options, + })); + + const rest = new REST({ version: "10" }).setToken(this.token); + + try { + this.logger.debug( + `Started refreshing ${slashCommands.length} application (/) commands.` + ); + await rest.put(Routes.applicationCommands(this.application.id), { + body: slashCommands, + }); + this.logger.info( + `Loaded ${slashCommands.length} application (/) commands.` + ); + } catch (error) { + this.logger.error("Failed to register commands:", error); + } + } +} diff --git a/src/structures/Command.ts b/src/structures/Command.ts new file mode 100644 index 0000000..27a42ed --- /dev/null +++ b/src/structures/Command.ts @@ -0,0 +1,121 @@ +import { + ApplicationCommandOptionData, + ChatInputCommandInteraction, + Message, +} from "discord.js"; +import { Context } from "./Context"; +import { Client } from "./Client"; +import { Argument } from "./Argument"; +import { currentClient } from "../context"; +import { MaybePromise } from "#types/extra.js"; +import { Logger } from "../utils/logger/Logger"; + +export interface CommandOptions { + name: string; + description: string; + aliases?: string[]; + options?: (ApplicationCommandOptionData | Argument)[]; + slash?: boolean; + prefix?: boolean; +} + +export class CommandBuilder { + public readonly client: Client; + public readonly logger: Logger; + + public readonly name: string; + public readonly description: string; + public readonly aliases: string[]; + public readonly options: ApplicationCommandOptionData[]; + private _supportsSlash: boolean; + private _supportsPrefix: boolean; + public _onMessage?: ( + ctx: NonNullable["toJSON"]>> + ) => MaybePromise; + public _onInteraction?: ( + ctx: NonNullable["toJSON"]>> + ) => MaybePromise; + + public get supportsSlash() { + return this._supportsSlash && this._onInteraction; + } + public get supportsPrefix() { + return this._supportsPrefix && this._onMessage; + } + constructor(options: CommandOptions) { + const client = currentClient; + if (!client) throw new Error("Client is not defined"); + this.client = client; + this.logger = client.logger; + + this.name = options.name; + this.description = options.description; + this.aliases = options.aliases ?? []; + + this.options = (options.options ?? []).map((opt) => { + return opt instanceof Argument ? opt.toJSON() : opt; + }); + this._supportsPrefix = options.prefix ?? false; + this._supportsSlash = options.slash ?? false; + + if (!this._supportsPrefix && !this._supportsSlash) { + throw new Error( + `Command ${this.name} must support either slash or prefix commands.` + ); + } + + if (this.client.commands.has(this.name)) + throw new Error(`Command name "${this.name}" is already registered.`); + + const existingAliasOwner = this.client.aliases.findKey((aliases) => + aliases.has(this.name) + ); + if (existingAliasOwner) { + throw new Error( + `Command name "${this.name}" is already registered as an alias for command "${existingAliasOwner}".` + ); + } + + for (const alias of this.aliases) { + if (this.client.commands.has(alias)) { + throw new Error( + `Alias "${alias}" is already registered as a command name.` + ); + } + const conflictingCommand = this.client.aliases.findKey((aliases) => + aliases.has(alias) + ); + if (conflictingCommand) { + throw new Error( + `Alias "${alias}" is already registered as an alias for command "${conflictingCommand}".` + ); + } + } + + this.client.commands.set(this.name, this); + if (this.aliases.length > 0) { + this.client.aliases.set(this.name, new Set(this.aliases)); + } + this.logger.debug(`Loaded Command ${this.name}`); + } + + onMessage( + func: ( + ctx: NonNullable["toJSON"]>> + ) => MaybePromise + ) { + this._onMessage = func; + return this; + } + + onInteraction( + func: ( + ctx: NonNullable< + ReturnType["toJSON"]> + > + ) => MaybePromise + ) { + this._onInteraction = func; + return this; + } +} diff --git a/src/structures/Context.ts b/src/structures/Context.ts new file mode 100644 index 0000000..d7484de --- /dev/null +++ b/src/structures/Context.ts @@ -0,0 +1,61 @@ +import { Message, User, ChatInputCommandInteraction } from "discord.js"; +import { Client } from "../structures/Client"; + +type ContextPayload = + T extends ChatInputCommandInteraction + ? { interaction: T; args?: string[] } + : { message: T; args?: string[] }; + +export class Context { + public readonly client: Client; + public readonly args: string[]; + public readonly data: T; + + constructor(client: Client, payload: ContextPayload) { + this.client = client; + this.args = payload.args ?? []; + + if ("interaction" in payload) { + this.data = payload.interaction as T; + } else { + this.data = payload.message as T; + } + } + + public isInteraction(): this is Context { + return this.data instanceof ChatInputCommandInteraction; + } + + public isMessage(): this is Context { + return this.data instanceof Message; + } + + public get author(): User | null { + if (this.isInteraction()) { + return this.data.user; + } + if (this.isMessage()) { + return this.data.author; + } + return null; + } + + toJSON() { + const { data, args, author } = this; + + if (this.isInteraction()) { + return { + kind: "interaction" as const, + interaction: data, + author: author, + }; + } + + return { + kind: "message" as const, + message: data as Message, + args: args, + author: author, + }; + } +} diff --git a/src/structures/Event.ts b/src/structures/Event.ts new file mode 100644 index 0000000..af5aa98 --- /dev/null +++ b/src/structures/Event.ts @@ -0,0 +1,65 @@ +import { ClientEvents } from "discord.js"; +import { MaybePromise } from "#types/extra.js"; +import { currentClient } from "../context"; +import { Client } from "./Client"; +import { Logger } from "../utils/logger/Logger"; + +type EventArgs = ClientEvents[K]; +type EventHandler = ( + context: EventBuilder, + ...args: EventArgs +) => MaybePromise; + +export class EventBuilder { + public readonly client: Client; + public readonly logger: Logger; + private handler?: EventHandler; + private bound = false; + + private readonly listener = async (...args: EventArgs) => { + if (!this.handler) return; + try { + await this.handler(this, ...args); + } catch (error) { + this.client.logger.error( + `Error executing event ${this.name} (${this.constructor.name}):`, + error + ); + } + }; + + constructor( + public readonly name: K, + public readonly once: boolean = false, + _handler?: EventHandler + ) { + if (!currentClient) throw new Error("Client is not defined"); + this.client = currentClient; + this.logger = currentClient.logger; + + if (_handler) { + this.handler = _handler; + this.register(); + } + + this.logger.debug(`Loaded Event ${String(this.name)}`); + } + + private register(): void { + if (this.bound || !this.handler) return; + + if (this.once) { + this.client.once(this.name as string, this.listener); + } else { + this.client.on(this.name as string, this.listener); + } + + this.bound = true; + } + + public onExecute(func: EventHandler) { + this.handler = func; + this.register(); + return this; + } +} diff --git a/src/utils/Files.ts b/src/utils/Files.ts new file mode 100644 index 0000000..307d934 --- /dev/null +++ b/src/utils/Files.ts @@ -0,0 +1,27 @@ +import FastGlob from "fast-glob"; +import path from "path"; + +export function getFiles(baseDir: string): string[] { + return FastGlob.sync(["**/*.ts", "**/*.js"], { + cwd: baseDir, + absolute: true, + ignore: [ + "**/*.d.ts", + "node_modules/**", + ".git/**", + "dist/**", + "lib/**", + "out/**", + "build/**", + ".next/**", + "coverage/**", + ], + }); +} +export function getProjectRoot(): string { + if (!require.main?.filename) { + return process.cwd(); + } + + return path.dirname(require.main.filename); +} diff --git a/src/utils/logger/ILogger.ts b/src/utils/logger/ILogger.ts new file mode 100644 index 0000000..8356b14 --- /dev/null +++ b/src/utils/logger/ILogger.ts @@ -0,0 +1,92 @@ +//https://github.com/sapphiredev/framework/tree/main/src/lib/utils/logger + +/** + * The logger levels for the {@link ILogger}. + */ +export enum LogLevel { + /** + * The lowest log level, used when calling {@link ILogger.trace}. + */ + Trace = 10, + + /** + * The debug level, used when calling {@link ILogger.debug}. + */ + Debug = 20, + + /** + * The info level, used when calling {@link ILogger.info}. + */ + Info = 30, + + /** + * The warning level, used when calling {@link ILogger.warn}. + */ + Warn = 40, + + /** + * The error level, used when calling {@link ILogger.error}. + */ + Error = 50, + + /** + * The critical level, used when calling {@link ILogger.fatal}. + */ + Fatal = 60, + + /** + * An unknown or uncategorized level. + */ + None = 100, +} + +export interface ILogger { + /** + * Checks whether a level is supported. + * @param level The level to check. + */ + has(level: LogLevel): boolean; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Trace} as level. + * @param values The values to log. + */ + trace(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Debug} as level. + * @param values The values to log. + */ + debug(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Info} as level. + * @param values The values to log. + */ + info(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Warn} as level. + * @param values The values to log. + */ + warn(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Error} as level. + * @param values The values to log. + */ + error(...values: readonly unknown[]): void; + + /** + * Alias of {@link ILogger.write} with {@link LogLevel.Fatal} as level. + * @param values The values to log. + */ + fatal(...values: readonly unknown[]): void; + + /** + * Writes the log message given a level and the value(s). + * @param level The log level. + * @param values The values to log. + */ + write(level: LogLevel, ...values: readonly unknown[]): void; +} diff --git a/src/utils/logger/Logger.ts b/src/utils/logger/Logger.ts new file mode 100644 index 0000000..037f4ad --- /dev/null +++ b/src/utils/logger/Logger.ts @@ -0,0 +1,392 @@ +//https://github.com/sapphiredev/plugins/tree/main/packages/logger +import { Console } from "console"; +import { inspect, type InspectOptions } from "util"; +import * as colorette from "colorette"; +import type { Color } from "colorette"; +import { Timestamp } from "@sapphire/timestamp"; +import type { ILogger } from "./ILogger"; +import { LogLevel } from "./ILogger"; + +export class Logger implements ILogger { + public level: LogLevel; + public readonly formats: Map; + public readonly join: string; + public readonly depth: number; + public readonly console: Console; + + private static instance: Logger | null = null; + private static readonly LOG_METHODS: ReadonlyMap = + new Map([ + [LogLevel.Trace, "trace"], + [LogLevel.Debug, "debug"], + [LogLevel.Info, "info"], + [LogLevel.Warn, "warn"], + [LogLevel.Error, "error"], + [LogLevel.Fatal, "error"], + ]); + + private static readonly DEFAULT_COLORS: ReadonlyMap = + new Map([ + [LogLevel.Trace, colorette.gray], + [LogLevel.Debug, colorette.magenta], + [LogLevel.Info, colorette.cyan], + [LogLevel.Warn, colorette.yellow], + [LogLevel.Error, colorette.red], + [LogLevel.Fatal, colorette.bgRed], + [LogLevel.None, colorette.white], + ]); + + private static readonly DEFAULT_NAMES: ReadonlyMap = + new Map([ + [LogLevel.Trace, "TRACE"], + [LogLevel.Debug, "DEBUG"], + [LogLevel.Info, "INFO"], + [LogLevel.Warn, "WARN"], + [LogLevel.Error, "ERROR"], + [LogLevel.Fatal, "FATAL"], + [LogLevel.None, ""], + ]); + + constructor(options: LoggerOptions = {}) { + this.level = options.level ?? LogLevel.Info; + this.console = new Console( + options.stdout ?? process.stdout, + options.stderr ?? process.stderr + ); + this.formats = this.createFormatMap( + options.format, + options.defaultFormat ?? options.format?.none ?? {} + ); + this.join = options.join ?? " "; + this.depth = options.depth ?? 2; + } + + static getInstance(): Logger { + if (!this.instance) { + this.instance = new Logger(); + } + return this.instance; + } + + static get stylize(): boolean { + return colorette.isColorSupported; + } + + setLevel(level: LogLevel): void { + this.level = level; + } + + has(level: LogLevel): boolean { + return level >= this.level; + } + + trace(...values: readonly unknown[]): void { + this.write(LogLevel.Trace, ...values); + } + + debug(...values: readonly unknown[]): void { + this.write(LogLevel.Debug, ...values); + } + + info(...values: readonly unknown[]): void { + this.write(LogLevel.Info, ...values); + } + + log(...values: readonly unknown[]): void { + this.write(LogLevel.Info, ...values); + } + + warn(...values: readonly unknown[]): void { + this.write(LogLevel.Warn, ...values); + } + + error(...values: readonly unknown[]): void { + this.write(LogLevel.Error, ...values); + } + + fatal(...values: readonly unknown[]): void { + this.write(LogLevel.Fatal, ...values); + } + + write(level: LogLevel, ...values: readonly unknown[]): void { + if (level < this.level) return; + + const method = Logger.LOG_METHODS.get(level); + const formatter = + this.formats.get(level) ?? this.formats.get(LogLevel.None)!; + const message = formatter.run(this.preprocess(values)); + + switch (method) { + case "trace": + this.console.trace(message); + break; + case "debug": + this.console.debug(message); + break; + case "info": + this.console.info(message); + break; + case "warn": + this.console.warn(message); + break; + case "error": + this.console.error(message); + break; + default: + this.console.log(message); + } + } + + protected preprocess(values: readonly unknown[]): string { + const inspectOptions: InspectOptions = { + colors: colorette.isColorSupported, + depth: this.depth, + }; + return values + .map((value) => + typeof value === "string" ? value : inspect(value, inspectOptions) + ) + .join(this.join); + } + + private createFormatMap( + options: LoggerFormatOptions = {}, + defaults: LoggerLevelOptions + ): Map { + const map = new Map(); + + for (const [level, color] of Logger.DEFAULT_COLORS) { + const name = Logger.DEFAULT_NAMES.get(level); + const levelOptions = + options[this.getLevelKey(level)] ?? + this.createDefaultLevel(defaults, color, name ?? ""); + map.set( + level, + levelOptions instanceof LoggerLevel + ? levelOptions + : new LoggerLevel(levelOptions) + ); + } + + return map; + } + + private createDefaultLevel( + defaults: LoggerLevelOptions, + color: Color, + name: string + ): LoggerLevel { + return new LoggerLevel({ + ...defaults, + timestamp: + defaults.timestamp === null ? null : { ...defaults.timestamp, color }, + infix: name.length ? `${color(name.padEnd(5, " "))} ` : "", + }); + } + + private getLevelKey(level: LogLevel): keyof LoggerFormatOptions { + const keys: Record = { + [LogLevel.Trace]: "trace", + [LogLevel.Debug]: "debug", + [LogLevel.Info]: "info", + [LogLevel.Warn]: "warn", + [LogLevel.Error]: "error", + [LogLevel.Fatal]: "fatal", + [LogLevel.None]: "none", + }; + return keys[level]; + } +} + +export class LoggerStyle { + public readonly style: Color; + + constructor(resolvable: LoggerStyleResolvable = {}) { + if (typeof resolvable === "function") { + this.style = resolvable; + } else { + const styles = this.buildStyleArray(resolvable); + this.style = this.combineStyles(styles); + } + } + + run(value: string | number): string { + return this.style(String(value)); + } + + private buildStyleArray(options: LoggerStyleOptions): Color[] { + const styles: Color[] = []; + + if (options.effects) { + styles.push(...options.effects.map((effect) => colorette[effect])); + } + + if (options.text) { + styles.push(colorette[options.text]); + } + + if (options.background) { + styles.push(colorette[options.background]); + } + + return styles; + } + + private combineStyles(styles: Color[]): Color { + if (styles.length === 0) return colorette.reset; + if (styles.length === 1) return styles[0]; + + return (text: string) => + styles.reduce((result, style) => style(result), text); + } +} + +export class LoggerTimestamp { + public timestamp: Timestamp; + public utc: boolean; + public color: LoggerStyle | null; + public formatter: LoggerTimestampFormatter; + + constructor(options: LoggerTimestampOptions = {}) { + this.timestamp = new Timestamp(options.pattern ?? "YYYY-MM-DD HH:mm:ss"); + this.utc = options.utc ?? false; + this.color = options.color === null ? null : new LoggerStyle(options.color); + this.formatter = options.formatter ?? ((timestamp) => `${timestamp} `); + } + + run(): string { + const date = new Date(); + + const result = this.utc + ? this.timestamp.displayUTC(date) + : this.timestamp.display(date); + + return this.formatter(this.color ? this.color.run(result) : result); + } +} + +export class LoggerLevel { + public timestamp: LoggerTimestamp | null; + public infix: string; + public message: LoggerStyle | null; + + constructor(options: LoggerLevelOptions = {}) { + this.timestamp = + options.timestamp === null + ? null + : new LoggerTimestamp(options.timestamp); + this.infix = options.infix ?? ""; + this.message = + options.message === null ? null : new LoggerStyle(options.message); + } + + run(content: string): string { + const prefix = (this.timestamp?.run() ?? "") + this.infix; + + if (prefix.length) { + const formatter = this.message + ? (line: string) => prefix + this.message?.run(line) + : (line: string) => prefix + line; + return content.split("\n").map(formatter).join("\n"); + } + + return this.message ? this.message.run(content) : content; + } +} + +export default Logger.getInstance(); + +export interface LoggerOptions { + stdout?: NodeJS.WritableStream; + stderr?: NodeJS.WritableStream; + defaultFormat?: LoggerLevelOptions; + format?: LoggerFormatOptions; + level?: LogLevel; + join?: string; + depth?: number; +} + +export interface LoggerFormatOptions { + trace?: LoggerLevelOptions; + debug?: LoggerLevelOptions; + info?: LoggerLevelOptions; + warn?: LoggerLevelOptions; + error?: LoggerLevelOptions; + fatal?: LoggerLevelOptions; + none?: LoggerLevelOptions; +} + +export interface LoggerLevelOptions { + timestamp?: LoggerTimestampOptions | null; + infix?: string; + message?: LoggerStyleResolvable | null; +} + +export interface LoggerTimestampOptions { + pattern?: string; + utc?: boolean; + color?: LoggerStyleResolvable | null; + formatter?: LoggerTimestampFormatter; +} + +export interface LoggerTimestampFormatter { + (timestamp: string): string; +} + +export interface LoggerStyleOptions { + effects?: LoggerStyleEffect[]; + text?: LoggerStyleText; + background?: LoggerStyleBackground; +} + +export type LoggerStyleResolvable = Color | LoggerStyleOptions; + +export enum LoggerStyleEffect { + Reset = "reset", + Bold = "bold", + Dim = "dim", + Italic = "italic", + Underline = "underline", + Inverse = "inverse", + Hidden = "hidden", + Strikethrough = "strikethrough", +} + +export enum LoggerStyleText { + Black = "black", + Red = "red", + Green = "green", + Yellow = "yellow", + Blue = "blue", + Magenta = "magenta", + Cyan = "cyan", + White = "white", + Gray = "gray", + BlackBright = "blackBright", + RedBright = "redBright", + GreenBright = "greenBright", + YellowBright = "yellowBright", + BlueBright = "blueBright", + MagentaBright = "magentaBright", + CyanBright = "cyanBright", + WhiteBright = "whiteBright", +} + +export enum LoggerStyleBackground { + Black = "bgBlack", + Red = "bgRed", + Green = "bgGreen", + Yellow = "bgYellow", + Blue = "bgBlue", + Magenta = "bgMagenta", + Cyan = "bgCyan", + White = "bgWhite", + BlackBright = "bgBlackBright", + RedBright = "bgRedBright", + GreenBright = "bgGreenBright", + YellowBright = "bgYellowBright", + BlueBright = "bgBlueBright", + MagentaBright = "bgMagentaBright", + CyanBright = "bgCyanBright", + WhiteBright = "bgWhiteBright", +} diff --git a/src/utils/util.ts b/src/utils/util.ts new file mode 100644 index 0000000..99b31b3 --- /dev/null +++ b/src/utils/util.ts @@ -0,0 +1,25 @@ +import { PrefixOptions } from "#types/client.js"; +import { InteractionResponse, Message } from "discord.js"; + +export function deleteMessage( + message: Message | InteractionResponse, + time = 15_000 +) { + return new Promise((r) => { + setTimeout(() => { + message.delete().catch(() => {}); + r(); + }, time); + }); +} +export function getPrefix(opts: PrefixOptions): string | false { + if (typeof opts === "string") { + return opts; + } + + if (opts.enabled && opts.prefix) { + return opts.prefix; + } + + return false; +} diff --git a/tsconfig.json b/tsconfig.json index cffc35f..572e823 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,8 @@ "outDir": "./dist", "rootDir": "./src", - "module": "CommonJS", - "moduleResolution": "Node", + "module": "nodenext", + "moduleResolution": "nodenext", "target": "esnext", diff --git a/types/client.d.ts b/types/client.d.ts new file mode 100644 index 0000000..4e1055d --- /dev/null +++ b/types/client.d.ts @@ -0,0 +1,19 @@ +import { ClientOptions } from "discord.js"; +import { LoggerOptions } from "../src/utils/logger/Logger"; + +export interface FrameworkPaths { + events?: string; + commands?: string; +} + +export type PrefixOptions = + | { enabled: true; prefix: string } + | { enabled: false } + | string; + +export interface FrameworkOptions extends ClientOptions { + logger?: LoggerOptions; + prefix?: PrefixOptions; + paths?: FrameworkPaths; + autoRegisterCommands?: boolean; +} diff --git a/types/extra.d.ts b/types/extra.d.ts new file mode 100644 index 0000000..5eb85a3 --- /dev/null +++ b/types/extra.d.ts @@ -0,0 +1 @@ +export type MaybePromise = Promise | T;