diff --git a/.gitignore b/.gitignore index d4bb0e3d..afb877a2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ Thumbs.db # Node node_modules +dist + +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 1fdeabad..a48d543d 100644 --- a/.prettierrc +++ b/.prettierrc @@ -6,6 +6,5 @@ "semi": true, "tabWidth": 4, "trailingComma": "all", - "useTabs": true, - "parser": "babel" -} + "useTabs": true +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index eabf7207..fac42fc6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,21 +1,34 @@ import globals from "globals"; import pluginJs from "@eslint/js"; +import tsParser from "@typescript-eslint/parser"; import stylisticJs from "@stylistic/eslint-plugin-js"; +import tsPlugin from "@typescript-eslint/eslint-plugin"; /** @type {import("eslint").Linter.Config[]} */ export default [ pluginJs.configs.recommended, { + files: ["**/*.ts"], + ignores: ["**/*.d.ts", "dist"], languageOptions: { globals: globals.node, ecmaVersion: "latest", sourceType: "module", + parser: tsParser, }, - ignores: ["node_modules", "dashboard"], plugins: { + "@typescript-eslint": tsPlugin, "@stylistic/js": stylisticJs, }, rules: { + "no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", // Игнорировать переменные, начинающиеся с _ + varsIgnorePattern: "^_", // Игнорировать переменные, начинающиеся с _ + ignoreRestSiblings: true, // Игнорировать неиспользуемые параметры в деструктуризации + }, + ], "arrow-body-style": ["error", "as-needed"], camelcase: "error", curly: ["error", "multi-line"], diff --git a/package.json b/package.json index ef00a397..3e1454e4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "src/index.js", "type": "module", "scripts": { - "start": "node src/index.js" + "start": "node dist/index.js", + "dev": "tsx watch src/index.ts", + "build": "tsc" }, "author": "https://github.com/JonnyBro", "dependencies": { @@ -31,9 +33,16 @@ "devDependencies": { "@eslint/js": "^9.16.0", "@stylistic/eslint-plugin-js": "^2.11.0", + "@types/md5": "^2.3.5", + "@types/ms": "^0.7.34", + "@types/node": "^22.10.5", + "@typescript-eslint/eslint-plugin": "^8.19.1", + "@typescript-eslint/parser": "^8.19.1", "eslint": "^9.16.0", "globals": "^15.13.0", "prettier": "^3.4.2", - "prettier-eslint": "^16.3.0" + "prettier-eslint": "^16.3.0", + "tsx": "^4.19.2", + "typescript": "^5.7.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6f96a07..b33b44b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 5.1.4 i18next: specifier: ^24.0.0 - version: 24.0.0(typescript@5.7.2) + version: 24.0.0(typescript@5.7.3) i18next-fs-backend: specifier: ^2.6.0 version: 2.6.0 @@ -69,6 +69,21 @@ importers: '@stylistic/eslint-plugin-js': specifier: ^2.11.0 version: 2.11.0(eslint@9.16.0) + '@types/md5': + specifier: ^2.3.5 + version: 2.3.5 + '@types/ms': + specifier: ^0.7.34 + version: 0.7.34 + '@types/node': + specifier: ^22.10.5 + version: 22.10.5 + '@typescript-eslint/eslint-plugin': + specifier: ^8.19.1 + version: 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.16.0)(typescript@5.7.3))(eslint@9.16.0)(typescript@5.7.3) + '@typescript-eslint/parser': + specifier: ^8.19.1 + version: 8.19.1(eslint@9.16.0)(typescript@5.7.3) eslint: specifier: ^9.16.0 version: 9.16.0 @@ -81,6 +96,12 @@ importers: prettier-eslint: specifier: ^16.3.0 version: 16.3.0 + tsx: + specifier: ^4.19.2 + version: 4.19.2 + typescript: + specifier: ^5.7.3 + version: 5.7.3 packages: @@ -146,6 +167,150 @@ packages: resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==} engines: {node: '>=16.11.0'} + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -348,8 +513,14 @@ packages: '@types/luxon@3.4.2': resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} - '@types/node@22.5.5': - resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} + '@types/md5@2.3.5': + resolution: {integrity: sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==} + + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} '@types/webidl-conversions@7.0.3': resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} @@ -360,6 +531,14 @@ packages: '@types/ws@8.5.12': resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} + '@typescript-eslint/eslint-plugin@8.19.1': + resolution: {integrity: sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + '@typescript-eslint/parser@6.21.0': resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -370,14 +549,36 @@ packages: typescript: optional: true + '@typescript-eslint/parser@8.19.1': + resolution: {integrity: sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + '@typescript-eslint/scope-manager@6.21.0': resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@8.19.1': + resolution: {integrity: sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.19.1': + resolution: {integrity: sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + '@typescript-eslint/types@6.21.0': resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/types@8.19.1': + resolution: {integrity: sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@6.21.0': resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -387,10 +588,27 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.19.1': + resolution: {integrity: sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/utils@8.19.1': + resolution: {integrity: sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + '@typescript-eslint/visitor-keys@6.21.0': resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/visitor-keys@8.19.1': + resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -410,11 +628,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} @@ -668,6 +881,11 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -802,6 +1020,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + gamedig@5.1.4: resolution: {integrity: sha512-MgSbNVGh5QMdrmRTrZ3W7W6sC5/Mx+dMgTy2uZCKQ9vns9eFXkQj61Pw2Y2FNHNMMp4DXFSUMYAPJWLcR16Wwg==} engines: {node: '>=16.20.0'} @@ -823,6 +1046,9 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1073,6 +1299,10 @@ packages: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1326,6 +1556,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} engines: {node: '>=14.16'} @@ -1490,12 +1723,23 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-api-utils@2.0.0: + resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1504,13 +1748,13 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} hasBin: true - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -1724,6 +1968,78 @@ snapshots: - bufferutil - utf-8-validate + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -1907,9 +2223,13 @@ snapshots: '@types/luxon@3.4.2': {} - '@types/node@22.5.5': + '@types/md5@2.3.5': {} + + '@types/ms@0.7.34': {} + + '@types/node@22.10.5': dependencies: - undici-types: 6.19.8 + undici-types: 6.20.0 '@types/webidl-conversions@7.0.3': {} @@ -1919,18 +2239,47 @@ snapshots: '@types/ws@8.5.12': dependencies: - '@types/node': 22.5.5 + '@types/node': 22.10.5 - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.16.0)(typescript@5.7.3))(eslint@9.16.0)(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.19.1(eslint@9.16.0)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/type-utils': 8.19.1(eslint@9.16.0)(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.16.0)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.19.1 + eslint: 9.16.0 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.2) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.3.7 eslint: 8.57.1 optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.19.1(eslint@9.16.0)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.19.1 + debug: 4.3.7 + eslint: 9.16.0 + typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -1939,9 +2288,27 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 + '@typescript-eslint/scope-manager@8.19.1': + dependencies: + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/visitor-keys': 8.19.1 + + '@typescript-eslint/type-utils@8.19.1(eslint@9.16.0)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.16.0)(typescript@5.7.3) + debug: 4.3.7 + eslint: 9.16.0 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@6.21.0': {} - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.7.2)': + '@typescript-eslint/types@8.19.1': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 @@ -1950,9 +2317,34 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) + ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.19.1(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/visitor-keys': 8.19.1 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.19.1(eslint@9.16.0)(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.16.0) + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + eslint: 9.16.0 + typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -1961,6 +2353,11 @@ snapshots: '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.19.1': + dependencies: + '@typescript-eslint/types': 8.19.1 + eslint-visitor-keys: 4.2.0 + '@ungap/structured-clone@1.2.0': {} '@vladfrangu/async_event_emitter@2.4.6': {} @@ -1973,8 +2370,6 @@ snapshots: dependencies: acorn: 8.14.0 - acorn@8.12.1: {} - acorn@8.14.0: {} agent-base@6.0.2: @@ -2253,6 +2648,33 @@ snapshots: entities@4.5.0: {} + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + escape-string-regexp@1.0.5: {} escape-string-regexp@4.0.0: {} @@ -2450,6 +2872,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.3: + optional: true + gamedig@5.1.4: dependencies: cheerio: 1.0.0-rc.12 @@ -2488,6 +2913,10 @@ snapshots: get-stream@6.0.1: {} + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2573,11 +3002,11 @@ snapshots: i18next-fs-backend@2.6.0: {} - i18next@24.0.0(typescript@5.7.2): + i18next@24.0.0(typescript@5.7.3): dependencies: '@babel/runtime': 7.25.6 optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 iconv-lite@0.6.3: dependencies: @@ -2636,7 +3065,7 @@ snapshots: jintr@3.0.2: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 js-yaml@4.1.0: dependencies: @@ -2724,6 +3153,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + minimist@1.2.8: {} minipass@3.3.6: @@ -2873,7 +3306,7 @@ snapshots: prettier-eslint@16.3.0: dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) common-tags: 1.8.2 dlv: 1.1.3 eslint: 8.57.1 @@ -2883,7 +3316,7 @@ snapshots: prettier: 3.4.2 pretty-format: 29.7.0 require-relative: 0.8.7 - typescript: 5.7.2 + typescript: 5.7.3 vue-eslint-parser: 9.4.3(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -2949,6 +3382,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + responselike@3.0.0: dependencies: lowercase-keys: 3.0.0 @@ -3100,23 +3535,34 @@ snapshots: dependencies: punycode: 2.3.1 - ts-api-utils@1.4.3(typescript@5.7.2): + ts-api-utils@1.4.3(typescript@5.7.3): dependencies: - typescript: 5.7.2 + typescript: 5.7.3 + + ts-api-utils@2.0.0(typescript@5.7.3): + dependencies: + typescript: 5.7.3 ts-mixer@6.0.4: {} tslib@2.7.0: {} + tsx@4.19.2: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 type-fest@0.20.2: {} - typescript@5.7.2: {} + typescript@5.7.3: {} - undici-types@6.19.8: {} + undici-types@6.20.0: {} undici@5.28.4: dependencies: diff --git a/src/adapters/cache/ICacheAdapter.js b/src/adapters/cache/ICacheAdapter.js deleted file mode 100644 index e4c49adc..00000000 --- a/src/adapters/cache/ICacheAdapter.js +++ /dev/null @@ -1,9 +0,0 @@ -export default class ICacheAdapter { - get() {} - - set() {} - - clear() {} - - delete() {} -} diff --git a/src/adapters/cache/ICacheAdapter.ts b/src/adapters/cache/ICacheAdapter.ts new file mode 100644 index 00000000..80fc8c85 --- /dev/null +++ b/src/adapters/cache/ICacheAdapter.ts @@ -0,0 +1,9 @@ +export default class ICacheAdapter { + get(_key: string) {} + + set(_key: string, _value: T) {} + + clear() {} + + delete(_key: string) {} +} diff --git a/src/adapters/cache/MapCache.js b/src/adapters/cache/MapCache.ts similarity index 73% rename from src/adapters/cache/MapCache.js rename to src/adapters/cache/MapCache.ts index 073af887..bab9ec2e 100644 --- a/src/adapters/cache/MapCache.js +++ b/src/adapters/cache/MapCache.ts @@ -1,16 +1,16 @@ import ICacheAdapter from "./ICacheAdapter.js"; export default class MapCache extends ICacheAdapter { + store = new Map(); constructor() { super(); - this.store = new Map(); } - get(key) { + get(key: string) { return this.store.get(key); } - set(key, value) { + set(key: string, value: T) { this.store.set(key, value); } @@ -18,7 +18,7 @@ export default class MapCache extends ICacheAdapter { this.store.clear(); } - delete(key) { + delete(key: string) { this.store.delete(key); } } diff --git a/src/adapters/database/IDatabaseAdapter.js b/src/adapters/database/IDatabaseAdapter.js deleted file mode 100644 index f844d775..00000000 --- a/src/adapters/database/IDatabaseAdapter.js +++ /dev/null @@ -1,25 +0,0 @@ -export default class IDatabaseAdapter { - async connect() { - throw new Error("Method `connect` not implemented."); - } - - async disconnect() { - throw new Error("Method `disconnect` not implemented."); - } - - async find() { - throw new Error("Method \"find\" must be implemented"); - } - - async findOne() { - throw new Error("Method \"findOne\" must be implemented"); - } - - async updateOne() { - throw new Error("Method \"updateOne\" must be implemented"); - } - - async deleteOne() { - throw new Error("Method \"deleteOne\" must be implemented"); - } -} diff --git a/src/adapters/database/IDatabaseAdapter.ts b/src/adapters/database/IDatabaseAdapter.ts new file mode 100644 index 00000000..6f6df16f --- /dev/null +++ b/src/adapters/database/IDatabaseAdapter.ts @@ -0,0 +1,9 @@ +export default abstract class IDatabaseAdapter { + abstract connect(): Promise; + abstract disconnect(): Promise; + abstract find(_model: ModelType, _query?: QueryType, _options?: OptionsType): Promise; + abstract findOne(_model: ModelType, _query?: QueryType, _options?: OptionsType): Promise; + abstract updateOne(_model: ModelType, _filter: QueryType, _update: UpdateType, _options?: OptionsType): Promise; + abstract deleteOne(_model: ModelType, _filter: QueryType): Promise; + abstract findOneOrCreate(_model: ModelType, _filter: QueryType): Promise; +} diff --git a/src/adapters/database/MongooseAdapter.js b/src/adapters/database/MongooseAdapter.ts similarity index 53% rename from src/adapters/database/MongooseAdapter.js rename to src/adapters/database/MongooseAdapter.ts index aa71f59c..871402c3 100644 --- a/src/adapters/database/MongooseAdapter.js +++ b/src/adapters/database/MongooseAdapter.ts @@ -1,24 +1,21 @@ -import mongoose from "mongoose"; +import mongoose, { ConnectOptions, Document, Model, FilterQuery, QueryOptions, UpdateQuery } from "mongoose"; import IDatabaseAdapter from "./IDatabaseAdapter.js"; -import logger from "../../helpers/logger.js"; +import logger from "@/helpers/logger.js"; import Cache from "../cache/MapCache.js"; -export default class MongooseAdapter extends IDatabaseAdapter { - /** - * - * @param {string} uri - database url connect - * @param {mongoose.ConnectOptions} options - database connect options - */ - constructor(uri, options = {}) { +export default class MongooseAdapter extends IDatabaseAdapter, FilterQuery, UpdateQuery, QueryOptions> { + cache = new Cache(); + options: ConnectOptions; + uri: string; + + constructor(uri: string, options: ConnectOptions = {}) { super(); if (!uri) { throw new Error("MongooseAdapter: URI is required."); } - - this.uri = uri; this.options = options; - this.cache = new Cache(); + this.uri = uri; } async connect() { @@ -32,11 +29,11 @@ export default class MongooseAdapter extends IDatabaseAdapter { this.cache.clear(); } - #generateCacheKey(modelName, query, options) { + #generateCacheKey(modelName: string, query: {}, options: {}) { return `${modelName}:${JSON.stringify(query)}:${JSON.stringify(options)}`; } - async find(model, query = {}, options = {}) { + async find(model: Model, query: FilterQuery, options = {}): Promise { const cacheKey = this.#generateCacheKey(model.modelName, query, options); if (this.cache.get(cacheKey)) { return this.cache.get(cacheKey); @@ -47,7 +44,7 @@ export default class MongooseAdapter extends IDatabaseAdapter { return result; } - async findOne(model, query = {}, options = {}) { + async findOne(model: Model, query: FilterQuery, options = {}): Promise { const cacheKey = this.#generateCacheKey(model.modelName, query, options); if (this.cache.get(cacheKey)) { return this.cache.get(cacheKey); @@ -58,15 +55,26 @@ export default class MongooseAdapter extends IDatabaseAdapter { return result; } - async updateOne(model, filter, update, options = {}) { + async updateOne(model: Model, filter: FilterQuery, update: UpdateQuery, options = {}) { const result = await model.updateOne(filter, update, options).exec(); this.cache.clear(); return result; } - async deleteOne(model, filter) { + async deleteOne(model: Model, filter: FilterQuery) { const result = await model.deleteOne(filter).exec(); this.cache.clear(); return result; } + + async findOneOrCreate(model: Model, filter: FilterQuery) { + this.cache.clear(); + const result = await model.findOne(filter).then(result => { + if (result) return result; + + return model.create(filter); + }); + + return result; + } } diff --git a/src/constants/index.js b/src/constants/index.ts similarity index 72% rename from src/constants/index.js rename to src/constants/index.ts index 9aaae42a..6d0748f6 100644 --- a/src/constants/index.js +++ b/src/constants/index.ts @@ -1,6 +1,7 @@ import path from "node:path"; -import { GatewayIntentBits } from "discord.js"; +import { GatewayIntentBits, MessageMentionOptions } from "discord.js"; import { AsyncLocalStorage } from "node:async_hooks"; +import { ExtendedClient } from "@/structures/client.js"; export const PROJECT_ROOT = path.join(import.meta.dirname, ".."); export const CONFIG_PATH = path.join(PROJECT_ROOT, "..", "config.json"); @@ -21,6 +22,6 @@ export const CLIENT_INTENTS = [ GatewayIntentBits.DirectMessages, GatewayIntentBits.DirectMessageReactions, ]; -export const CLIENT_ALLOWED_MENTIONS = { parse: ["everyone", "roles", "users"] }; +export const CLIENT_ALLOWED_MENTIONS: MessageMentionOptions = { parse: ["everyone", "roles", "users"] }; -export const SUPER_CONTEXT = new AsyncLocalStorage(); +export const SUPER_CONTEXT = new AsyncLocalStorage(); diff --git a/src/events/Ready.js b/src/events/Ready.ts similarity index 68% rename from src/events/Ready.js rename to src/events/Ready.ts index db26db4f..4cc94ee6 100644 --- a/src/events/Ready.js +++ b/src/events/Ready.ts @@ -1,18 +1,15 @@ import logger from "../helpers/logger.js"; import { resolve } from "node:path"; -import loadCronTasks from "../utils/loadCronTasks.js"; -import { CronManager } from "../services/cron/index.js"; +import loadCronTasks from "@/utils/loadCronTasks.js"; +import { CronManager } from "@/services/cron/index.js"; +import { ExtendedClient } from "@/structures/client.js"; export const data = { name: "ready", once: true, }; -/** - * - * @param {import("../structures/client.js").ExtendedClient} client - */ -export async function run(client) { +export async function run(client: ExtendedClient) { logger.ready(client.user.tag + " is online!"); // Fetching all app emojis, because we need to use them diff --git a/src/handlers/command-handler/functions/registerCommands.js b/src/handlers/command-handler/functions/registerCommands.ts similarity index 60% rename from src/handlers/command-handler/functions/registerCommands.js rename to src/handlers/command-handler/functions/registerCommands.ts index e9d6955c..fc88de35 100644 --- a/src/handlers/command-handler/functions/registerCommands.js +++ b/src/handlers/command-handler/functions/registerCommands.ts @@ -1,27 +1,30 @@ -import logger from "../../../helpers/logger.js"; +import { ExtendedClient } from "@/structures/client.js"; +import logger from "@/helpers/logger.js"; import differentCommands from "../utils/differentcommands.js"; +import { CommandFileObject } from "@/types.js"; +import { ApplicationCommandData, GuildApplicationCommandManager } from "discord.js"; -export default async function registerCommands(props) { +type RegisterCommandProps = { + client: ExtendedClient; + commands: CommandFileObject[]; +}; + +export default async function registerCommands(props: RegisterCommandProps) { props.client.once("ready", () => handleRegistration(props.client, props.commands)); } -const handleRegistration = async (client, commands) => { +const handleRegistration = async (client: ExtendedClient, commands: CommandFileObject[]) => { const devOnlyCommands = commands.filter(cmd => cmd.options?.devOnly); const globalCommands = commands.filter(cmd => !cmd.options?.devOnly); - const devGuildsIds = client.configService.get("devGuildsIds"); + const devGuildsIds = client.configService.get("devGuildsIds"); await registerGlobalCommands(client, globalCommands); await registerDevCommands(client, devOnlyCommands, devGuildsIds); }; -/** - * - * @param {import("../../../structures/client.js").ExtendedClient} client - * @param {*} commands - */ -const registerGlobalCommands = async (client, commands) => { - const appCommandsManager = client.application.commands; +const registerGlobalCommands = async (client: ExtendedClient, commands: CommandFileObject[]) => { + const appCommandsManager = client.application!.commands; await appCommandsManager.fetch(); await Promise.all( @@ -29,7 +32,7 @@ const registerGlobalCommands = async (client, commands) => { const targetCommand = appCommandsManager.cache.find(cmd => cmd.name === data.name); if (targetCommand && differentCommands(targetCommand, data)) { - await targetCommand.edit(data).catch(() => logger.error(`Failed to update command: ${data.name} globally`)); + await targetCommand.edit(data as Partial).catch(() => logger.error(`Failed to update command: ${data.name} globally`)); logger.log(`Edited command globally: ${data.name}`); } else if (!targetCommand) { @@ -38,15 +41,10 @@ const registerGlobalCommands = async (client, commands) => { }), ); - logger.log("Registered global commands"); + logger.log(`Registered ${commands.length} global commands`); }; -/** - * - * @param {import("../../../structures/client.js").ExtendedClient} client - * @param {*} commands - */ -const registerDevCommands = async (client, commands, guildsIds) => { +const registerDevCommands = async (client: ExtendedClient, commands: CommandFileObject[], guildsIds: string[]) => { const devGuilds = []; for (const guildId of guildsIds) { @@ -60,7 +58,7 @@ const registerDevCommands = async (client, commands, guildsIds) => { devGuilds.push(guild); } - const guildCommandsManagers = []; + const guildCommandsManagers: GuildApplicationCommandManager[] = []; for (const guild of devGuilds) { const guildCommandsManager = guild.commands; @@ -74,7 +72,7 @@ const registerDevCommands = async (client, commands, guildsIds) => { guildCommandsManagers.map(async guildCommands => { const targetCommand = guildCommands.cache.find(cmd => cmd.name === data.name); if (targetCommand && differentCommands(targetCommand, data)) { - await targetCommand.edit(data).catch(() => logger.error(`Failed to update command: ${data.name} in ${guildCommands.guild.name} server`)); + await targetCommand.edit(data as Partial).catch(() => logger.error(`Failed to update command: ${data.name} in ${guildCommands.guild.name} server`)); logger.log(`Edited command globally: ${data.name}`); } else if (!targetCommand) { diff --git a/src/handlers/command-handler/index.js b/src/handlers/command-handler/index.ts similarity index 78% rename from src/handlers/command-handler/index.js rename to src/handlers/command-handler/index.ts index 54078119..93c6ebd0 100644 --- a/src/handlers/command-handler/index.js +++ b/src/handlers/command-handler/index.ts @@ -1,16 +1,16 @@ import { resolve } from "node:path"; -import logger from "../../helpers/logger.js"; -import { getFilePaths } from "../../utils/index.js"; -import { toFileURL } from "../../utils/resolve-file.js"; +import logger from "@/helpers/logger.js"; +import { getFilePaths } from "@/utils/get-path.js"; +import { toFileURL } from "@/utils/resolve-file.js"; import registerCommands from "./functions/registerCommands.js"; +import { ExtendedClient } from "@/structures/client.js"; +import { CommandFileObject } from "@//types.js"; export class CommandHandler { - constructor(client) { - /** - * @type {import("../../structures/client.js").ExtendedClient} client - */ + client: ExtendedClient; + commands: CommandFileObject[] = []; + constructor(client: ExtendedClient) { this.client = client; - this.commands = []; } async init() { @@ -26,7 +26,7 @@ export class CommandHandler { async #buildCommands() { const cmdPath = resolve(this.client.configService.get("paths.commands")); - const commandFilePaths = (await getFilePaths(cmdPath, true)).filter(path => path.endsWith(".js")); + const commandFilePaths = (await getFilePaths(cmdPath, true)).filter(path => path.endsWith(".js") || path.endsWith(".ts")); for (const cmdFilePath of commandFilePaths) { const { data, run } = await import(toFileURL(cmdFilePath)); @@ -58,7 +58,7 @@ export class CommandHandler { // Skip if autocomplete handler is not defined if (isAutocomplete && !targetCommand.autocompleteRun) return; - const command = targetCommand[isAutocomplete ? "autocompleteRun" : "run"]; + const command = targetCommand[isAutocomplete ? "autocompleteRun" : "run"]!; try { await command({ diff --git a/src/handlers/command-handler/utils/differentcommands.js b/src/handlers/command-handler/utils/differentcommands.ts similarity index 79% rename from src/handlers/command-handler/utils/differentcommands.js rename to src/handlers/command-handler/utils/differentcommands.ts index bf32572c..e4a6d7ac 100644 --- a/src/handlers/command-handler/utils/differentcommands.js +++ b/src/handlers/command-handler/utils/differentcommands.ts @@ -1,4 +1,4 @@ -export default function differentCommands(appCommand, localCommand) { +export default function differentCommands(appCommand: any, localCommand: any) { const appOptions = appCommand.options || []; const localOptions = localCommand.options || []; const appDescription = appCommand.description || ""; diff --git a/src/handlers/event-handler/index.js b/src/handlers/event-handler/index.ts similarity index 56% rename from src/handlers/event-handler/index.js rename to src/handlers/event-handler/index.ts index 9bb3b915..ad0ce3dd 100644 --- a/src/handlers/event-handler/index.js +++ b/src/handlers/event-handler/index.ts @@ -1,12 +1,24 @@ import { resolve } from "node:path"; -import logger from "../../helpers/logger.js"; -import { getFilePaths } from "../../utils/index.js"; -import { toFileURL } from "../../utils/resolve-file.js"; -import { useMainPlayer } from "discord-player"; +import logger from "@/helpers/logger.js"; +import { getFilePaths } from "@/utils/get-path.js"; +import { toFileURL } from "@/utils/resolve-file.js"; +import { GuildQueueEvents, useMainPlayer } from "discord-player"; +import { ExtendedClient } from "@/structures/client.js"; +import { ClientEvents } from "discord.js"; + +interface EventHandlerEvents { + data: { + name: keyof ClientEvents; + once?: boolean; + player?: boolean; + }; + run: Function; +} export class EventHandler { - constructor(client) { - this.events = []; + events: EventHandlerEvents[] = []; + client: ExtendedClient; + constructor(client: ExtendedClient) { this.client = client; } @@ -18,7 +30,7 @@ export class EventHandler { async #buildEvents() { try { const eventPath = resolve(this.client.configService.get("paths.events")); - const eventFilePaths = (await getFilePaths(eventPath, true)).filter(path => path.endsWith(".js")); + const eventFilePaths = (await getFilePaths(eventPath, true)).filter(path => path.endsWith(".js") || path.endsWith(".ts")); for (const eventFilePath of eventFilePaths) { const { data, run } = await import(toFileURL(eventFilePath)); @@ -42,11 +54,9 @@ export class EventHandler { } $registerEvents() { - const player = useMainPlayer(); for (const { data, run } of this.events) { - if (data.player) player.events.on(data.name, run); - if (data.once) this.client.once(data.name, run); - else this.client.on(data.name, run); + if (data.once) this.client.once(data.name, (...args) => run(...args)); + else this.client.on(data.name, (...args) => run(...args)); } } } diff --git a/src/handlers/index.js b/src/handlers/index.ts similarity index 68% rename from src/handlers/index.js rename to src/handlers/index.ts index 7d0d4bcd..83c7ad29 100644 --- a/src/handlers/index.js +++ b/src/handlers/index.ts @@ -1,14 +1,16 @@ +import { ExtendedClient } from "@/structures/client.js"; import { CommandHandler } from "./command-handler/index.js"; import { EventHandler } from "./event-handler/index.js"; export class Handlers { - constructor(client) { + client: ExtendedClient; + constructor(client: ExtendedClient) { this.client = client; - this.#init(); + this.init(); } - async #init() { + private async init() { const eventHandler = new EventHandler(this.client); await eventHandler.init(); diff --git a/src/helpers/cleanup.js b/src/helpers/cleanup.js deleted file mode 100644 index 59cce4e0..00000000 --- a/src/helpers/cleanup.js +++ /dev/null @@ -1,68 +0,0 @@ -// Thanks Stackoverflow <3 -function setDaysTimeout(callback, days) { - // 86400 seconds in a day - const msInDay = 86400 * 1000; - - let dayCount = 0; - const timer = setInterval(function () { - dayCount++; // a day has passed - - if (dayCount === days) { - clearInterval(timer); - callback.apply(this, []); - } - }, msInDay); -} - -/** - * - * @param {import("../base/Client")} client - */ -export async function init(client) { - setDaysTimeout(async () => { - const timestamp = Date.now() + 29 * 24 * 60 * 60 * 1000; // 29 days - const members = client.membersData.find({ transactions: { $gt: [] } }); - - for (const member of members) { - const transactions = member.transactions; - for await (const transaction of transactions) { - if (transaction.date < timestamp) { - const index = transactions.indexOf(transaction); - transactions.splice(index, 1); - - await member.save(); - } - } - } - }, 14); - - setDaysTimeout(async () => { - client.usersData.find({}, function (err, res) { - for (const user of res) { - client.users.fetch(user.id).then(async u => { - if (u.username.match(/.*Deleted User.* [A-z0-9]+/g)) { - await client.databaseCache.users.delete(u.id); - await client.usersData.deleteOne({ id: u.id }); - client.logger.log(`Removed from database deleted user - ID: ${u.id} Username: ${u.username}`); - - await client.usersData.save(); - } - }); - } - }); - - client.membersData.find({}, function (err, res) { - for (const user of res) { - client.users.fetch(user.id).then(async u => { - if (u.username.match(/.*Deleted User.* [A-z0-9]+/g)) { - await client.databaseCache.members.delete(u.id); - await client.membersData.deleteOne({ id: u.id }); - client.logger.log(`Removed from database deleted user - ID: ${u.id} Username: ${u.username}`); - - await client.membersData.save(); - } - }); - } - }); - }, 30); -} diff --git a/src/helpers/extenders.js b/src/helpers/extenders.ts similarity index 64% rename from src/helpers/extenders.js rename to src/helpers/extenders.ts index 32460c0e..c44bfe82 100644 --- a/src/helpers/extenders.js +++ b/src/helpers/extenders.ts @@ -1,43 +1,26 @@ -import { BaseInteraction } from "discord.js"; -import useClient from "../utils/use-client.js"; +import { BaseInteraction, User } from "discord.js"; +import useClient from "@/utils/use-client.js"; -export const getLocale = guildId => { +export const getLocale = async (guildId: string) => { const client = useClient(); - const guild = client.getGuildData(guildId); + const guild = await client.getGuildData(guildId); return guild.language; }; const getAppEmojis = () => { const client = useClient(); - return client.application.emojis.cache; + return client.application?.emojis.cache; }; -/** - * - * @param {import('../structures/client.js').ExtendedClient} client - * @param {string} prefixEmoji - * @param {string} message - */ -const formatReply = (prefixEmoji, message) => { - const emojis = getAppEmojis(); +const formatReply = (prefixEmoji: string, message: string) => { + const emojis = getAppEmojis()!; return prefixEmoji ? `${emojis.find(e => e.name === prefixEmoji).toString()} ${message}` : `${message}`; }; -/** - * - * @param {import("discord.js").User} user - * @returns - */ -export const getUsername = user => (user.discriminator === "0" ? user.username : user.tag); -/** - * - * @param {import("discord.js").Message | import("discord.js").BaseInteraction} context - * @param {string} key - * @param {unknown[]} args - * @param {*} options - */ +export const getUsername = (user: User) => (user.discriminator === "0" ? user.username : user.tag); + export const replyTranslated = async (context, key, args, options = {}) => { const client = useClient(); const locale = options.locale || client.configService.get("defaultLang"); diff --git a/src/helpers/functions.js b/src/helpers/functions.js deleted file mode 100644 index 1de5ae4d..00000000 --- a/src/helpers/functions.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Asynchronously iterates over a collection and executes a callback function for each item. - * - * @param {any[]} collection - The collection to iterate over. - * @param {(item: any) => Promise} callback - The async callback function to execute for each item in the collection. - * @returns {Promise} A promise that resolves when all items in the collection have been processed. - */ -export async function asyncForEach(collection, callback) { - const allPromises = collection.map(async key => { - await callback(key); - }); - - return await Promise.all(allPromises); -} - -/** - * Sorts an array by the specified key in ascending order. - * - * @param {any[]} array - The array to sort. - * @param {string} key - The key to sort the array by. - * @returns {any[]} The sorted array. - */ -export function sortByKey(array, key) { - return array.sort(function (a, b) { - const x = a[key]; - const y = b[key]; - return x < y ? 1 : x > y ? -1 : 0; - }); -} - -/** - * Shuffles the elements of the provided array in-place. - * - * @param {any[]} pArray - The array to shuffle. - * @returns {any[]} The shuffled array. - */ -export function shuffle(pArray) { - const array = []; - - pArray.forEach(element => array.push(element)); - - let currentIndex = array.length, - temporaryValue, - randomIndex; - - while (currentIndex !== 0) { - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - - return array; -} - -/** - * Generates a random integer between the specified minimum and maximum values (inclusive). - * - * @param {number} [min=0] - The minimum value (inclusive). - * @param {number} [max=100] - The maximum value (inclusive). - * @returns {number} A random integer between min and max. - */ -export function randomNum(min = 0, max = 100) { - return (Math.random() * (max - min + 1)) << 0; -} - -/** - * Formats a date for the specified client and locale. - * - * @param {string} date - The date to format. - * @param {Intl.LocalesArgument} [locale] - The locale to use for formatting the date. - * @returns {string} The formatted date. - */ -export function printDate(date, locale = "en-US") { - return new Intl.DateTimeFormat(locale).format(date); -} - -/** - * Generates the appropriate noun form based on the given number and noun forms. - * - * @param {number} number - The number to use for determining the noun form. - * @param {Array} wordForms - An array of three elements: [one, two, five]. - * @returns {string} The appropriate noun form based on the given number. - */ -export function getNoun(number, wordForms) { - if (!Array.isArray(wordForms) || wordForms.length !== 3) { - throw new Error("wordForms should be an array with three elements: [one, two, five]"); - } - - const [one, two, five] = wordForms; - let n = Math.abs(number); - n %= 100; - if (n >= 5 && n <= 20) return five; - n %= 10; - if (n === 1) return one; - if (n >= 2 && n <= 4) return two; - return five; -} diff --git a/src/helpers/functions.ts b/src/helpers/functions.ts new file mode 100644 index 00000000..7673ad2b --- /dev/null +++ b/src/helpers/functions.ts @@ -0,0 +1,58 @@ +export async function asyncForEach(collection: T[], callback: (_item: T) => Promise) { + const allPromises = collection.map(async key => { + await callback(key); + }); + + return await Promise.all(allPromises); +} + +export function sortByKey(array: T[], key: string) { + return array.sort(function (a, b) { + const x = a[key]; + const y = b[key]; + return x < y ? 1 : x > y ? -1 : 0; + }); +} + +export function shuffle(pArray: T[]) { + const array: T[] = []; + + pArray.forEach(element => array.push(element)); + + let currentIndex = array.length, + temporaryValue, + randomIndex; + + while (currentIndex !== 0) { + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + + return array; +} + +export function randomNum(min: number = 0, max: number = 100) { + return (Math.random() * (max - min + 1)) << 0; +} + +export function printDate(date: Date | number, locale: Intl.LocalesArgument = "en-US") { + return new Intl.DateTimeFormat(locale).format(date); +} + +export function getNoun(number: number, wordForms: string[]) { + if (!Array.isArray(wordForms) || wordForms.length !== 3) { + throw new Error("wordForms should be an array with three elements: [one, two, five]"); + } + + const [one, two, five] = wordForms; + let n = Math.abs(number); + n %= 100; + if (n >= 5 && n <= 20) return five; + n %= 10; + if (n === 1) return one; + if (n >= 2 && n <= 4) return two; + return five; +} diff --git a/src/helpers/logger.js b/src/helpers/logger.ts similarity index 81% rename from src/helpers/logger.js rename to src/helpers/logger.ts index 5352756e..27e280f2 100644 --- a/src/helpers/logger.js +++ b/src/helpers/logger.ts @@ -1,6 +1,6 @@ import chalk from "chalk"; -function format(tDate) { +function format(tDate: Date | number) { return new Intl.DateTimeFormat("ru-RU", { year: "numeric", month: "2-digit", @@ -21,27 +21,27 @@ const logLevels = { }; export default { - log(...content) { + log(...content: unknown[]) { return console.log(`[${format(Date.now())}]: ${logLevels.LOG} ${content.join(" ")}`); }, - warn(...content) { + warn(...content: unknown[]) { return console.log(`[${format(Date.now())}]: ${logLevels.WARN} ${content.join(" ")}`); }, - error(...content) { + error(...content: unknown[]) { return console.log(`[${format(Date.now())}]: ${logLevels.ERROR} ${content.join(" ")}`); }, - debug(...content) { + debug(...content: unknown[]) { return console.log(`[${format(Date.now())}]: ${logLevels.DEBUG} ${content.join(" ")}`); }, - cmd(...content) { + cmd(...content: unknown[]) { return console.log(`[${format(Date.now())}]: ${logLevels.CMD} ${content.join(" ")}`); }, - ready(...content) { + ready(...content: unknown[]) { return console.log(`[${format(Date.now())}]: ${logLevels.READY} ${content.join(" ")}`); }, }; diff --git a/src/helpers/tasks/birthdays.js b/src/helpers/tasks/birthdays.ts similarity index 75% rename from src/helpers/tasks/birthdays.js rename to src/helpers/tasks/birthdays.ts index 99d558d9..752fea19 100644 --- a/src/helpers/tasks/birthdays.js +++ b/src/helpers/tasks/birthdays.ts @@ -1,6 +1,6 @@ -import useClient from "../../utils/use-client.js"; -import UserModel from "../../models/UserModel.js"; -import { createEmbed } from "../../utils/create-embed.js"; +import useClient from "@/utils/use-client.js"; +import UserModel from "@/models/UserModel.js"; +import { createEmbed } from "@/utils/create-embed.js"; import logger from "../logger.js"; import { getNoun } from "../functions.js"; @@ -10,7 +10,9 @@ export const data = { const client = useClient(); const guilds = client.guilds.cache.values(); - const users = await client.adapter.find(UserModel, { birthdate: { $gt: 1 } }); + const users = await client.adapter.find(UserModel, { + birthdate: { $ne: null }, + }); const currentData = new Date(); const currentYear = currentData.getFullYear(); @@ -24,11 +26,15 @@ export const data = { if (!channel) return; + if (!channel.isSendable()) return; + const userIDs = users.filter(u => guild.members.cache.has(u.id)).map(u => u.id); await Promise.all( userIDs.map(async userID => { const user = users.find(u => u.id === userID); + if (!user) return; + const userData = new Date(user.birthdate).getFullYear() <= 1970 ? new Date(user.birthdate * 1000) : new Date(user.birthdate); const userYear = userData.getFullYear(); const userMonth = userData.getMonth(); @@ -38,7 +44,9 @@ export const data = { if (userDate === currentDate && userMonth === currentMonth) { const embed = createEmbed({ - author: client.user.username, + author: { + name: client.user.username, + }, fields: [ { name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", { @@ -47,11 +55,7 @@ export const data = { value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", { lng: data.language, user: user.id, - age: `**${age}** ${getNoun(age, [ - client.translate("misc:NOUNS:AGE:1", null, data.language), - client.translate("misc:NOUNS:AGE:2", null, data.language), - client.translate("misc:NOUNS:AGE:5", null, data.language), - ])}`, + age: `**${age}** ${getNoun(age, [client.translate("misc:NOUNS:AGE:1", data.language), client.translate("misc:NOUNS:AGE:2", data.language), client.translate("misc:NOUNS:AGE:5", data.language)])}`, }), }, ], diff --git a/src/helpers/tasks/checkReminds.js b/src/helpers/tasks/checkReminds.ts similarity index 60% rename from src/helpers/tasks/checkReminds.js rename to src/helpers/tasks/checkReminds.ts index 1fa09a71..a59fed36 100644 --- a/src/helpers/tasks/checkReminds.js +++ b/src/helpers/tasks/checkReminds.ts @@ -1,8 +1,9 @@ +import { createEmbed } from "@/utils/create-embed.js"; import UserModel from "../../models/UserModel.js"; import useClient from "../../utils/use-client.js"; -import { createEmbed } from "../../utils/index.js"; +import { CronTaskData } from "@/types.js"; -export const data = { +export const data: CronTaskData = { name: "checkReminds", task: async () => { const client = useClient(); @@ -11,17 +12,19 @@ export const data = { for (const user of users) { if (!client.users.cache.has(user.id)) client.users.fetch(user.id); - client.cacheReminds.set(user.id, user); + client.cacheReminds.set(user.id, { + id: user.id, + reminds: user.reminds, + }); } }); - client.cacheReminds.forEach(async user => { - const cachedUser = client.users.cache.get(user.id); + client.cacheReminds.forEach(async ({ id, reminds }) => { + const cachedUser = client.users.cache.get(id); if (!cachedUser) return; - const reminds = user.reminds, - mustSent = reminds.filter(r => r.sendAt < Math.floor(Date.now() / 1000)); + const mustSent = reminds.filter(r => r.sendAt < Math.floor(Date.now() / 1000)); if (!mustSent.length) return; @@ -53,15 +56,23 @@ export const data = { embeds: [embed], }) .then(() => { - client.adapter.updateOne(UserModel, { id: user.id }, { $pull: { reminds: { _id: r._id } } }); + client.adapter.updateOne( + UserModel, + { id }, + { + $pull: { + reminds: { + sendAt: r.sendAt, + }, + }, + }, + ); }); }); - user.reminds = user.reminds.filter(r => r.sendAt >= Math.floor(Date.now() / 1000)); + reminds = reminds.filter(r => r.sendAt >= Math.floor(Date.now() / 1000)); - await user.save(); - - if (!user.reminds.length) client.cacheReminds.delete(user.id); + if (!reminds.length) client.cacheReminds.delete(id); }); }, schedule: "* * * * * *", diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts diff --git a/src/models/GuildModel.js b/src/models/GuildModel.js deleted file mode 100644 index d52c2a54..00000000 --- a/src/models/GuildModel.js +++ /dev/null @@ -1,59 +0,0 @@ -import { model, Schema } from "mongoose"; -import useClient from "../utils/use-client.js"; - -const client = useClient(); - -export default model( - "Guild", - new Schema({ - id: { type: String }, - - membersData: { type: Object, default: {} }, - members: [{ type: Schema.Types.ObjectId, ref: "Member" }], - - language: { type: String, default: client.configService.get("defaultLang") }, - plugins: { - type: Object, - default: { - welcome: { - enabled: false, - message: null, - channel: null, - withImage: null, - }, - goodbye: { - enabled: false, - message: null, - channel: null, - withImage: null, - }, - autorole: { - enabled: false, - role: null, - }, - automod: { - enabled: false, - ignored: [], - }, - warnsSanctions: { - kick: null, - ban: null, - }, - monitoring: { - messageUpdate: null, - messageDelete: null, - }, - tickets: { - count: 0, - ticketLogs: null, - transcriptionLogs: null, - ticketsCategory: null, - }, - suggestions: null, - reports: null, - birthdays: null, - modlogs: null, - }, - }, - }), -); diff --git a/src/models/GuildModel.ts b/src/models/GuildModel.ts new file mode 100644 index 00000000..ffc78858 --- /dev/null +++ b/src/models/GuildModel.ts @@ -0,0 +1,68 @@ +import { model, Schema, Types, Document } from "mongoose"; +import useClient from "@/utils/use-client.js"; + +const client = useClient(); + +interface IGuildSchema extends Document { + id: string; + membersData: { + [key: string]: any; + }; + members: Types.ObjectId[]; + language: string; + plugins: any; +} + +const GuildSchema = new Schema({ + id: { type: String }, + + membersData: { type: Object, default: {} }, + members: [{ type: Schema.Types.ObjectId, ref: "Member" }], + + language: { type: String, default: client.configService.get("defaultLang") }, + plugins: { + type: Object, + default: { + welcome: { + enabled: false, + message: null, + channel: null, + withImage: null, + }, + goodbye: { + enabled: false, + message: null, + channel: null, + withImage: null, + }, + autorole: { + enabled: false, + role: null, + }, + automod: { + enabled: false, + ignored: [], + }, + warnsSanctions: { + kick: null, + ban: null, + }, + monitoring: { + messageUpdate: null, + messageDelete: null, + }, + tickets: { + count: 0, + ticketLogs: null, + transcriptionLogs: null, + ticketsCategory: null, + }, + suggestions: null, + reports: null, + birthdays: null, + modlogs: null, + }, + }, +}); + +export default model("Guild", GuildSchema); diff --git a/src/models/MemberModel.js b/src/models/MemberModel.js deleted file mode 100644 index 59aeec04..00000000 --- a/src/models/MemberModel.js +++ /dev/null @@ -1,36 +0,0 @@ -import { model, Schema } from "mongoose"; - -export default model( - "Member", - new Schema({ - id: { type: String }, - guildID: { type: String }, - - money: { type: Number, default: 0 }, - workStreak: { type: Number, default: 0 }, - bankSold: { type: Number, default: 0 }, - exp: { type: Number, default: 0 }, - level: { type: Number, default: 0 }, - transactions: { type: Array, default: [] }, - - registeredAt: { type: Number, default: Date.now() }, - - cooldowns: { - type: Object, - default: { - work: 0, - rob: 0, - }, - }, - - sanctions: { type: Array, default: [] }, - mute: { - type: Object, - default: { - muted: false, - case: null, - endDate: null, - }, - }, - }), -); diff --git a/src/models/MemberModel.ts b/src/models/MemberModel.ts new file mode 100644 index 00000000..e0c2683a --- /dev/null +++ b/src/models/MemberModel.ts @@ -0,0 +1,57 @@ +import { Schema, Document, model } from "mongoose"; + +interface IMemberSchema extends Document { + id: string; + guildID: string; + money: number; + workStreak: number; + bankSold: number; + exp: number; + level: number; + transactions: string[]; + registeredAt: number; + cooldowns: { + work: number; + rob: number; + }; + sanctions: string[]; + mute: { + muted: boolean; + case: string | null; + endDate: number | null; + }; +} + +const memberSchema = new Schema({ + id: { type: String }, + guildID: { type: String, ref: "Guild" }, + + money: { type: Number, default: 0 }, + workStreak: { type: Number, default: 0 }, + bankSold: { type: Number, default: 0 }, + exp: { type: Number, default: 0 }, + level: { type: Number, default: 0 }, + transactions: [String], + + registeredAt: { type: Number, default: Date.now() }, + + cooldowns: { + type: Object, + default: { + work: 0, + rob: 0, + }, + }, + + sanctions: [String], + mute: { + type: Object, + default: { + muted: false, + case: null, + endDate: null, + }, + }, +}); + +export default model("Member", memberSchema); diff --git a/src/models/UserModel.js b/src/models/UserModel.ts similarity index 77% rename from src/models/UserModel.js rename to src/models/UserModel.ts index e86ba80b..8a086ceb 100644 --- a/src/models/UserModel.js +++ b/src/models/UserModel.ts @@ -1,6 +1,37 @@ -import { Schema, model } from "mongoose"; +import { Schema, model, Document } from "mongoose"; import { createCanvas, loadImage } from "@napi-rs/canvas"; +export type UserReminds = { + message: string; + createdAt: number; + sendAt: number; +}; + +interface IUserSchema extends Document { + id: string; + rep: number; + bio: string; + birthdate: number; + lover: string; + registeredAt: number; + achievements: { + [key: string]: { + achieved: boolean; + progress: { + now: number; + total: number; + }; + }; + }; + cooldowns: { + rep: number; + }; + afk: string; + reminds: UserReminds[]; + logged: boolean; + apiToken: string; +} + const genToken = () => { let token = ""; const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzy0123456789.-_"; @@ -9,7 +40,7 @@ const genToken = () => { return token; }; -const userSchema = new Schema({ +const userSchema = new Schema({ id: { type: String }, rep: { type: Number, default: 0 }, @@ -82,7 +113,16 @@ const userSchema = new Schema({ }, afk: { type: String, default: null }, - reminds: { type: Array, default: [] }, + reminds: [ + { + type: Object, + default: { + message: null, + createdAt: null, + sendAt: null, + }, + }, + ], logged: { type: Boolean, default: false }, apiToken: { type: String, default: genToken() }, }); @@ -110,4 +150,4 @@ userSchema.method("getAchievements", async function () { return await canvas.encode("png"); }); -export default model("User", userSchema); +export default model("User", userSchema); diff --git a/src/services/config/index.js b/src/services/config/index.ts similarity index 56% rename from src/services/config/index.js rename to src/services/config/index.ts index d4a67192..9983d25d 100644 --- a/src/services/config/index.js +++ b/src/services/config/index.ts @@ -1,28 +1,16 @@ import fs from "fs"; -import { CONFIG_PATH } from "../../constants/index.js"; -import logger from "../../helpers/logger.js"; +import { CONFIG_PATH } from "@/constants/index.js"; +import logger from "@/helpers/logger.js"; class ConfigService { - constructor() { - this.config = this.#loadConfig(); - } + config = this.loadConfig(); - /** - * - * @param {string} key - key of the config - * @returns {*} - value of the config - */ - get(key) { + get(key: string): T { const keys = key.split("."); return keys.reduce((config, k) => (config && config[k] !== undefined ? config[k] : undefined), this.config); } - /** - * Set a config value. - * @param {string} key - key of the config to set - * @param {*} value - value to set - */ - set(key, value) { + set(key: string, value: unknown) { const keys = key.split("."); keys.reduce((config, k, i) => { if (i === keys.length - 1) { @@ -32,14 +20,10 @@ class ConfigService { } return config[k]; }, this.config); - this.#saveConfig(); + this.saveConfig(); } - /** - * Load the config from the file. - * @returns {Config} - loaded config - */ - #loadConfig() { + private loadConfig() { if (fs.existsSync(CONFIG_PATH)) { return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")); } else { @@ -48,10 +32,7 @@ class ConfigService { } } - /** - * Save the config to the file. - */ - #saveConfig() { + private saveConfig() { try { fs.writeFileSync(CONFIG_PATH, JSON.stringify(this.config, null, 4), "utf-8"); } catch (e) { diff --git a/src/services/cron/index.js b/src/services/cron/index.ts similarity index 79% rename from src/services/cron/index.js rename to src/services/cron/index.ts index 6bf721d3..aef7663a 100644 --- a/src/services/cron/index.js +++ b/src/services/cron/index.ts @@ -1,10 +1,12 @@ import { CronJob } from "cron"; import logger from "../../helpers/logger.js"; +import { CronTaskData } from "@/types.js"; export class CronManager { - constructor(tasks) { + jobs = new Map(); + tasks: CronTaskData[]; + constructor(tasks: CronTaskData[]) { this.tasks = tasks; - this.jobs = new Map(); } async init() { @@ -31,7 +33,7 @@ export class CronManager { await task.task(); } catch (error) { logger.error(`Error executing cron task "${task.name}":`, error); - this.jobs.get(task.name).isError = true; + this.jobs.get(task.name)!.isError = true; } }, null, @@ -50,9 +52,8 @@ export class CronManager { } stopAll() { - if (!this.jobs.length) return; + if (!this.jobs.size) return; - // eslint-disable-next-line no-unused-vars for (const [_, jobInfo] of this.jobs.entries()) { jobInfo.job.stop(); } diff --git a/src/services/languages/index.js b/src/services/languages/index.ts similarity index 55% rename from src/services/languages/index.js rename to src/services/languages/index.ts index 2fde72f5..d8a920c1 100644 --- a/src/services/languages/index.js +++ b/src/services/languages/index.ts @@ -1,40 +1,50 @@ -import i18next from "i18next"; +import i18next, { TOptionsBase } from "i18next"; import Backend from "i18next-fs-backend"; import fs from "fs/promises"; import { resolve, join } from "path"; -import logger from "../../helpers/logger.js"; +import logger from "@/helpers/logger.js"; import supportedLanguages from "./language-meta.js"; +import { ExtendedClient } from "@/structures/client.js"; + +interface InternationalizationServiceOptions { + defaultLanguage?: string; +} + +interface WalkDirectoryResult { + namespaces: string[]; + languages: string[]; +} export default class InternationalizationService { - /** - * Constructs an instance of the InternationalizationService. - * - * @param {import("../../structures/client").ExtendedClient} client - The client instance. - * @param {Object} [options={}] - Optional configuration options. - */ - constructor(client, options = {}) { + private client: ExtendedClient; + private options: { + localesPath: string; + defaultLanguage: string; + }; + + constructor(client: ExtendedClient, options: InternationalizationServiceOptions = {}) { this.client = client; this.options = { localesPath: resolve(this.client.configService.get("paths.locales")), defaultLanguage: options.defaultLanguage || "en-US", }; - this.i18next = this.#init(); + this.init(); } - get getSupportedLanguages() { + get getSupportedLanguages(): string[] { return supportedLanguages.map(lang => lang.locale); } - async #walkDirectory(dir, namespaces = [], folderName = "") { + private async walkDirectory(dir: string, namespaces: string[] = [], folderName: string = ""): Promise { const files = await fs.readdir(dir, { withFileTypes: true }); - const languages = []; + const languages: string[] = []; for (const file of files) { if (file.isDirectory()) { const isLanguage = file.name.includes("-"); if (isLanguage) languages.push(file.name); - const folder = await this.#walkDirectory(join(dir, file.name), namespaces, isLanguage ? "" : `${file.name}/`); + const folder = await this.walkDirectory(join(dir, file.name), namespaces, isLanguage ? "" : `${file.name}/`); namespaces = folder.namespaces; } else { @@ -44,10 +54,10 @@ export default class InternationalizationService { return { namespaces: [...new Set(namespaces)], languages }; } - async #init() { - const { namespaces, languages } = await this.#walkDirectory(this.options.localesPath); + private async init() { + const { namespaces, languages } = await this.walkDirectory(this.options.localesPath); - const i18n = await i18next.use(Backend).init({ + const i18nInstance = await i18next.use(Backend).init({ backend: { loadPath: resolve(this.options.localesPath, "./{{lng}}/{{ns}}.json"), }, @@ -61,13 +71,14 @@ export default class InternationalizationService { initImmediate: false, }); - this.client.translate = (key, options = {}) => { + // Типизированная функция translate + this.client.translate = (key: string, options: TOptionsBase = {}): string => { const lng = options.lng || this.options.defaultLanguage; return i18next.t(key, { lng, ...options }); }; logger.log("Internationalization initialized"); - return i18n; + return i18nInstance; } } diff --git a/src/services/languages/language-meta.js b/src/services/languages/language-meta.ts similarity index 100% rename from src/services/languages/language-meta.js rename to src/services/languages/language-meta.ts diff --git a/src/structures/client.js b/src/structures/client.js deleted file mode 100644 index c01ea128..00000000 --- a/src/structures/client.js +++ /dev/null @@ -1,98 +0,0 @@ -import { Client } from "discord.js"; -import { Player } from "discord-player"; -import MongooseAdapter from "../adapters/database/MongooseAdapter.js"; -import logger from "../helpers/logger.js"; -import { Handlers } from "../handlers/index.js"; -import ConfigService from "../services/config/index.js"; -import InternationalizationService from "../services/languages/index.js"; -import { SUPER_CONTEXT } from "../constants/index.js"; - -export class ExtendedClient extends Client { - /** - * @param {import("discord.js").ClientOptions} options - */ - constructor(options) { - if (SUPER_CONTEXT.getStore()) { - return SUPER_CONTEXT.getStore(); - } - super(options); - - this.configService = new ConfigService(); - this.adapter = new MongooseAdapter(this.configService.get("mongoDB")); - this.i18n = new InternationalizationService(this); - this.cacheReminds = new Map(); - new Player(this); - new Handlers(this); - - SUPER_CONTEXT.enterWith(this); - } - - async init() { - try { - await this.adapter.connect(); - - await this.login(this.configService.get("token")); - } catch (error) { - logger.error(error); - } - } - - /** - * Retrieves a guild data object from the database. - * @param {string} guildId - The ID of the guild to find or create. - * @returns {Promise} The guild data object, either retrieved from the database or newly created. - */ - async getGuildData(guildId) { - const { default: GuildModel } = await import("../models/GuildModel.js"); - let guildData = await this.adapter.findOne(GuildModel, { id: guildId }); - - if (!guildData) { - guildData = new GuildModel({ id: guildId }); - await guildData.save(); - } - - return guildData; - } - - /** - * Returns a User data from the database. - * @param {string} userID - The ID of the user to find or create. - * @returns {Promise} The user data object, either retrieved from the database or newly created. - */ - async getUserData(userID) { - const { default: UserModel } = await import("../models/UserModel.js"); - let userData = await this.adapter.findOne(UserModel, { id: userID }); - - if (!userData) { - userData = new UserModel({ id: userID }); - await userData.save(); - } - - return userData; - } - - /** - * Returns a Member data from the database. - * @param {string} memberId - The ID of the member to find or create. - * @param {string} guildId - The ID of the guild the member belongs to. - * @returns {Promise} The member data object, either retrieved from the database or newly created. - */ - async getMemberData(memberId, guildId) { - const { default: MemberModel } = await import("../models/MemberModel.js"); - let memberData = await this.adapter.findOne(MemberModel, { guildID: guildId, id: memberId }); - - if (!memberData) { - memberData = new MemberModel({ id: memberId, guildID: guildId }); - await memberData.save(); - - const guildData = await this.getGuildData(guildId); - - if (guildData) { - guildData.members.push(memberData._id); - await guildData.save(); - } - } - - return memberData; - } -} diff --git a/src/structures/client.ts b/src/structures/client.ts new file mode 100644 index 00000000..6fef258b --- /dev/null +++ b/src/structures/client.ts @@ -0,0 +1,69 @@ +import { Client, ClientOptions } from "discord.js"; +import { TOptionsBase } from "i18next"; +import { Handlers } from "@/handlers/index.js"; +import MongooseAdapter from "@/adapters/database/MongooseAdapter.js"; +import logger from "@/helpers/logger.js"; +import ConfigService from "@/services/config/index.js"; +import InternationalizationService from "@/services/languages/index.js"; +import { SUPER_CONTEXT } from "@/constants/index.js"; +import { cacheRemindsData } from "@/types.js"; + +export class ExtendedClient extends Client { + configService = new ConfigService(); + adapter = new MongooseAdapter(this.configService.get("mongoDB")); + cacheReminds = new Map(); + i18n = new InternationalizationService(this); + translate!: ( + _key: string, + _options?: + | TOptionsBase + | { + [key: string]: string; + }, + ) => string; + + constructor(options: ClientOptions) { + if (SUPER_CONTEXT.getStore()) { + return SUPER_CONTEXT.getStore() as ExtendedClient; + } + super(options); + + new Handlers(this); + SUPER_CONTEXT.enterWith(this); + } + + async init() { + try { + await this.adapter.connect(); + await this.login(this.configService.get("token")); + } catch (error) { + logger.error(error); + } + } + + async getGuildData(guildId: string) { + const { default: GuildModel } = await import("@/models/GuildModel.js"); + const guildData = await this.adapter.findOneOrCreate(GuildModel, { id: guildId }); + + return guildData; + } + + async getUserData(userID: string) { + const { default: UserModel } = await import("@/models/UserModel.js"); + const userData = await this.adapter.findOneOrCreate(UserModel, { id: userID }); + + return userData; + } + + async getMemberData(memberId: string, guildID: string) { + const { default: MemberModel } = await import("@/models/MemberModel.js"); + const memberData = await this.adapter.findOneOrCreate(MemberModel, { id: memberId, guildID }); + + const guildData = await this.getGuildData(guildID); + + guildData.members.push(memberData.id); + await guildData.save(); + + return memberData; + } +} diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 00000000..60a4fed8 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,53 @@ +import { + AutocompleteInteraction, + CacheType, + ChatInputCommandInteraction, + ContextMenuCommandInteraction, + Interaction, + MessageContextMenuCommandInteraction, + PermissionsString, + RESTPostAPIApplicationCommandsJSONBody, + UserContextMenuCommandInteraction, +} from "discord.js"; +import { ExtendedClient } from "./structures/client.ts"; +import { UserReminds } from "./models/UserModel.ts"; + +export type CommandData = RESTPostAPIApplicationCommandsJSONBody; + +export type cacheRemindsData = { + id: string; + reminds: UserReminds[]; +}; + +export type CronTaskData = { + name: string; + schedule: string; + task: () => Promise | void; +}; + +export interface CommandProps { + interaction: ChatInputCommandInteraction | ContextMenuCommandInteraction | UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction | AutocompleteInteraction; + client: ExtendedClient; +} + +export interface SlashCommandProps extends CommandProps { + interaction: ChatInputCommandInteraction; +} + +export interface CommandContext<_T extends Interaction, _Cached extends CacheType> { + interaction: Interaction; + client: ExtendedClient; +} + +export interface CommandOptions { + devOnly?: boolean; + userPermissions?: PermissionsString | PermissionsString[]; + botPermissions?: PermissionsString | PermissionsString[]; +} + +export interface CommandFileObject { + data: CommandData; + options?: CommandOptions; + run: (_ctx: CommandContext) => Awaited; + autocompleteRun?: (_ctx: CommandContext) => Awaited; +} diff --git a/src/utils/create-embed.js b/src/utils/create-embed.js deleted file mode 100644 index c2b94d03..00000000 --- a/src/utils/create-embed.js +++ /dev/null @@ -1,14 +0,0 @@ -import { EmbedBuilder } from "discord.js"; -import useClient from "../utils/use-client.js"; -/** - * - * @param {import("discord.js").EmbedData} data - embed data - * @returns The generated EmbedBuilder instance. - */ -export const createEmbed = data => { - const client = useClient(); - return new EmbedBuilder({ - footer: typeof data.footer === "object" ? data.footer : data.footer ? { text: data.footer } : client.configService.get("embed.footer"), - ...data, - }).setColor(data.color ?? client.configService.get("embed.color")); -}; diff --git a/src/utils/create-embed.ts b/src/utils/create-embed.ts new file mode 100644 index 00000000..b3abb6d8 --- /dev/null +++ b/src/utils/create-embed.ts @@ -0,0 +1,10 @@ +import { EmbedBuilder, EmbedData } from "discord.js"; +import useClient from "./use-client.js"; + +export const createEmbed = (data: EmbedData) => { + const client = useClient(); + return new EmbedBuilder({ + footer: typeof data.footer === "object" ? data.footer : data.footer ? { text: data.footer } : client.configService.get("embed.footer"), + ...data, + }).setColor(data.color || client.configService.get("embed.color")); +}; diff --git a/src/utils/get-path.js b/src/utils/get-path.ts similarity index 63% rename from src/utils/get-path.js rename to src/utils/get-path.ts index 805847d7..1cd47c30 100644 --- a/src/utils/get-path.js +++ b/src/utils/get-path.ts @@ -1,11 +1,9 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { PROJECT_ROOT } from "../constants/index.js"; +import { PROJECT_ROOT } from "@/constants/index.js"; -export const getFilePaths = async (directory, nesting) => { - let filePaths = []; - - if (!directory) return; +export const getFilePaths = async (directory: string, nesting: boolean) => { + let filePaths: string[] = []; const absoluteDirectory = path.isAbsolute(directory) ? directory : path.join(PROJECT_ROOT, directory); @@ -19,7 +17,8 @@ export const getFilePaths = async (directory, nesting) => { } if (nesting && file.isDirectory()) { - filePaths = [...filePaths, ...(await getFilePaths(filePath, true))]; + const nestedFiles = await getFilePaths(filePath, true); + filePaths = [...filePaths, ...nestedFiles]; } } diff --git a/src/utils/index.js b/src/utils/index.js deleted file mode 100644 index 0a323cbb..00000000 --- a/src/utils/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./create-embed.js"; -export * from "./get-path.js"; -export * from "./resolve-file.js"; diff --git a/src/utils/loadCronTasks.js b/src/utils/loadCronTasks.ts similarity index 74% rename from src/utils/loadCronTasks.js rename to src/utils/loadCronTasks.ts index ff50c4e7..45589fd3 100644 --- a/src/utils/loadCronTasks.js +++ b/src/utils/loadCronTasks.ts @@ -1,15 +1,16 @@ +import { CronTaskData } from "@/types.js"; import logger from "../helpers/logger.js"; import { getFilePaths } from "./get-path.js"; import { toFileURL } from "./resolve-file.js"; -const loadCronTasks = async taskPath => { +const loadCronTasks = async (taskPath: string): Promise => { try { - const filePaths = (await getFilePaths(taskPath, true)).filter(file => file.endsWith(".js")); + const filePaths = (await getFilePaths(taskPath, true)).filter(file => file.endsWith(".js") || file.endsWith(".ts")); const tasks = []; for (const filePath of filePaths) { - const { data } = await import(toFileURL(filePath)); + const { data } = (await import(toFileURL(filePath))) as { data: CronTaskData }; if (!data) continue; diff --git a/src/utils/resolve-file.js b/src/utils/resolve-file.js deleted file mode 100644 index 4f2727d9..00000000 --- a/src/utils/resolve-file.js +++ /dev/null @@ -1,12 +0,0 @@ -import path from "node:path"; -import { PROJECT_ROOT } from "../constants/index.js"; - -/** - * Convert a local file path to a file URL. - * @param {string} filePath - local file's path. - * @returns {string} file URL - */ -export const toFileURL = filePath => { - const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(PROJECT_ROOT, filePath); - return "file://" + resolvedPath.replace(/\\\\|\\/g, "/"); -}; diff --git a/src/utils/resolve-file.ts b/src/utils/resolve-file.ts new file mode 100644 index 00000000..0b88d85b --- /dev/null +++ b/src/utils/resolve-file.ts @@ -0,0 +1,7 @@ +import path from "node:path"; +import { PROJECT_ROOT } from "@/constants/index.js"; + +export const toFileURL = (filePath: string) => { + const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(PROJECT_ROOT, filePath); + return "file://" + resolvedPath.replace(/\\\\|\\/g, "/"); +}; diff --git a/src/utils/use-client.js b/src/utils/use-client.js deleted file mode 100644 index 3514ce05..00000000 --- a/src/utils/use-client.js +++ /dev/null @@ -1,16 +0,0 @@ -import { SUPER_CONTEXT } from "../constants/index.js"; - -/** - * Returns the instance of the client. - * - * @throws {Error} Client is not initialized. Please initialize it first. - * - * @returns {import("../structures/client.js").ExtendedClient} The client instance. - */ -export default function useClient() { - const store = SUPER_CONTEXT.getStore(); - if (!store) { - throw new Error("Client is not initialized. Please initialize it first."); - } - return store; -} diff --git a/src/utils/use-client.ts b/src/utils/use-client.ts new file mode 100644 index 00000000..b44b8db8 --- /dev/null +++ b/src/utils/use-client.ts @@ -0,0 +1,9 @@ +import { SUPER_CONTEXT } from "@/constants/index.js"; + +export default function useClient() { + const store = SUPER_CONTEXT.getStore(); + if (!store) { + throw new Error("Client is not initialized. Please initialize it first."); + } + return store; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..606dd8cf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", // Версия ECMAScript + "module": "NodeNext", // Использование ES-модулей + "moduleResolution": "nodenext", // Разрешение модулей + "outDir": "./dist", // Директория для скомпилированных файлов + "rootDir": "./src", // Директория с исходным кодом + "strict": true, // Включение строгой проверки типов + "esModuleInterop": true, // Для совместимости с CommonJS + "skipLibCheck": true, // Пропуск проверки типов в библиотеках + "forceConsistentCasingInFileNames": true, // Единообразие в именах файлов + "resolveJsonModule": true, // Разрешение импорта JSON-файлов + "isolatedModules": true, // Изолированные модули + "incremental": true, // Инкрементальная сборка (ускоряет повторную компиляцию, сохраняя результаты предыдущих компиляции) + "noEmitOnError": true, // Чтобы не билдил без ошибок + "noUnusedLocals": true, // Не использовать неиспользуемые локальные переменные + "noUnusedParameters": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*"], // Включаемые файлы + "exclude": ["node_modules"] // Исключаемые файлы +}