feat: added TS support

This commit is contained in:
Slincnik 2025-01-11 23:02:14 +03:00
parent 78fca15044
commit eb78c29c90
No known key found for this signature in database
48 changed files with 1124 additions and 673 deletions

3
.gitignore vendored
View file

@ -13,3 +13,6 @@ Thumbs.db
# Node # Node
node_modules node_modules
dist
tsconfig.tsbuildinfo

View file

@ -6,6 +6,5 @@
"semi": true, "semi": true,
"tabWidth": 4, "tabWidth": 4,
"trailingComma": "all", "trailingComma": "all",
"useTabs": true, "useTabs": true
"parser": "babel" }
}

View file

@ -1,21 +1,34 @@
import globals from "globals"; import globals from "globals";
import pluginJs from "@eslint/js"; import pluginJs from "@eslint/js";
import tsParser from "@typescript-eslint/parser";
import stylisticJs from "@stylistic/eslint-plugin-js"; import stylisticJs from "@stylistic/eslint-plugin-js";
import tsPlugin from "@typescript-eslint/eslint-plugin";
/** @type {import("eslint").Linter.Config[]} */ /** @type {import("eslint").Linter.Config[]} */
export default [ export default [
pluginJs.configs.recommended, pluginJs.configs.recommended,
{ {
files: ["**/*.ts"],
ignores: ["**/*.d.ts", "dist"],
languageOptions: { languageOptions: {
globals: globals.node, globals: globals.node,
ecmaVersion: "latest", ecmaVersion: "latest",
sourceType: "module", sourceType: "module",
parser: tsParser,
}, },
ignores: ["node_modules", "dashboard"],
plugins: { plugins: {
"@typescript-eslint": tsPlugin,
"@stylistic/js": stylisticJs, "@stylistic/js": stylisticJs,
}, },
rules: { rules: {
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_", // Игнорировать переменные, начинающиеся с _
varsIgnorePattern: "^_", // Игнорировать переменные, начинающиеся с _
ignoreRestSiblings: true, // Игнорировать неиспользуемые параметры в деструктуризации
},
],
"arrow-body-style": ["error", "as-needed"], "arrow-body-style": ["error", "as-needed"],
camelcase: "error", camelcase: "error",
curly: ["error", "multi-line"], curly: ["error", "multi-line"],

View file

@ -5,7 +5,9 @@
"main": "src/index.js", "main": "src/index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node src/index.js" "start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"build": "tsc"
}, },
"author": "https://github.com/JonnyBro", "author": "https://github.com/JonnyBro",
"dependencies": { "dependencies": {
@ -31,9 +33,16 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.16.0", "@eslint/js": "^9.16.0",
"@stylistic/eslint-plugin-js": "^2.11.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", "eslint": "^9.16.0",
"globals": "^15.13.0", "globals": "^15.13.0",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"prettier-eslint": "^16.3.0" "prettier-eslint": "^16.3.0",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
} }
} }

View file

@ -46,7 +46,7 @@ importers:
version: 5.1.4 version: 5.1.4
i18next: i18next:
specifier: ^24.0.0 specifier: ^24.0.0
version: 24.0.0(typescript@5.7.2) version: 24.0.0(typescript@5.7.3)
i18next-fs-backend: i18next-fs-backend:
specifier: ^2.6.0 specifier: ^2.6.0
version: 2.6.0 version: 2.6.0
@ -69,6 +69,21 @@ importers:
'@stylistic/eslint-plugin-js': '@stylistic/eslint-plugin-js':
specifier: ^2.11.0 specifier: ^2.11.0
version: 2.11.0(eslint@9.16.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: eslint:
specifier: ^9.16.0 specifier: ^9.16.0
version: 9.16.0 version: 9.16.0
@ -81,6 +96,12 @@ importers:
prettier-eslint: prettier-eslint:
specifier: ^16.3.0 specifier: ^16.3.0
version: 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: packages:
@ -146,6 +167,150 @@ packages:
resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==} resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==}
engines: {node: '>=16.11.0'} 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': '@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -348,8 +513,14 @@ packages:
'@types/luxon@3.4.2': '@types/luxon@3.4.2':
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
'@types/node@22.5.5': '@types/md5@2.3.5':
resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} 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': '@types/webidl-conversions@7.0.3':
resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==}
@ -360,6 +531,14 @@ packages:
'@types/ws@8.5.12': '@types/ws@8.5.12':
resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} 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': '@typescript-eslint/parser@6.21.0':
resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
@ -370,14 +549,36 @@ packages:
typescript: typescript:
optional: true 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': '@typescript-eslint/scope-manager@6.21.0':
resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==}
engines: {node: ^16.0.0 || >=18.0.0} 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': '@typescript-eslint/types@6.21.0':
resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==}
engines: {node: ^16.0.0 || >=18.0.0} 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': '@typescript-eslint/typescript-estree@6.21.0':
resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
@ -387,10 +588,27 @@ packages:
typescript: typescript:
optional: true 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': '@typescript-eslint/visitor-keys@6.21.0':
resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==}
engines: {node: ^16.0.0 || >=18.0.0} 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': '@ungap/structured-clone@1.2.0':
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
@ -410,11 +628,6 @@ packages:
peerDependencies: peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 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: acorn@8.14.0:
resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
@ -668,6 +881,11 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'} 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: escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@ -802,6 +1020,11 @@ packages:
fs.realpath@1.0.0: fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 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: gamedig@5.1.4:
resolution: {integrity: sha512-MgSbNVGh5QMdrmRTrZ3W7W6sC5/Mx+dMgTy2uZCKQ9vns9eFXkQj61Pw2Y2FNHNMMp4DXFSUMYAPJWLcR16Wwg==} resolution: {integrity: sha512-MgSbNVGh5QMdrmRTrZ3W7W6sC5/Mx+dMgTy2uZCKQ9vns9eFXkQj61Pw2Y2FNHNMMp4DXFSUMYAPJWLcR16Wwg==}
engines: {node: '>=16.20.0'} engines: {node: '>=16.20.0'}
@ -823,6 +1046,9 @@ packages:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'} engines: {node: '>=10'}
get-tsconfig@4.8.1:
resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==}
glob-parent@5.1.2: glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -1073,6 +1299,10 @@ packages:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'} 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: minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
@ -1326,6 +1556,9 @@ packages:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'} engines: {node: '>=4'}
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
responselike@3.0.0: responselike@3.0.0:
resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
@ -1490,12 +1723,23 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=4.2.0' 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: ts-mixer@6.0.4:
resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==}
tslib@2.7.0: tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} 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: type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -1504,13 +1748,13 @@ packages:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
typescript@5.7.2: typescript@5.7.3:
resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
undici-types@6.19.8: undici-types@6.20.0:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
undici@5.28.4: undici@5.28.4:
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
@ -1724,6 +1968,78 @@ snapshots:
- bufferutil - bufferutil
- utf-8-validate - 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)': '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)':
dependencies: dependencies:
eslint: 8.57.1 eslint: 8.57.1
@ -1907,9 +2223,13 @@ snapshots:
'@types/luxon@3.4.2': {} '@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: dependencies:
undici-types: 6.19.8 undici-types: 6.20.0
'@types/webidl-conversions@7.0.3': {} '@types/webidl-conversions@7.0.3': {}
@ -1919,18 +2239,47 @@ snapshots:
'@types/ws@8.5.12': '@types/ws@8.5.12':
dependencies: 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: dependencies:
'@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 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 '@typescript-eslint/visitor-keys': 6.21.0
debug: 4.3.7 debug: 4.3.7
eslint: 8.57.1 eslint: 8.57.1
optionalDependencies: 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: transitivePeerDependencies:
- supports-color - supports-color
@ -1939,9 +2288,27 @@ snapshots:
'@typescript-eslint/types': 6.21.0 '@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 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/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: dependencies:
'@typescript-eslint/types': 6.21.0 '@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0
@ -1950,9 +2317,34 @@ snapshots:
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 9.0.3 minimatch: 9.0.3
semver: 7.6.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: 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: transitivePeerDependencies:
- supports-color - supports-color
@ -1961,6 +2353,11 @@ snapshots:
'@typescript-eslint/types': 6.21.0 '@typescript-eslint/types': 6.21.0
eslint-visitor-keys: 3.4.3 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': {} '@ungap/structured-clone@1.2.0': {}
'@vladfrangu/async_event_emitter@2.4.6': {} '@vladfrangu/async_event_emitter@2.4.6': {}
@ -1973,8 +2370,6 @@ snapshots:
dependencies: dependencies:
acorn: 8.14.0 acorn: 8.14.0
acorn@8.12.1: {}
acorn@8.14.0: {} acorn@8.14.0: {}
agent-base@6.0.2: agent-base@6.0.2:
@ -2253,6 +2648,33 @@ snapshots:
entities@4.5.0: {} 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@1.0.5: {}
escape-string-regexp@4.0.0: {} escape-string-regexp@4.0.0: {}
@ -2450,6 +2872,9 @@ snapshots:
fs.realpath@1.0.0: {} fs.realpath@1.0.0: {}
fsevents@2.3.3:
optional: true
gamedig@5.1.4: gamedig@5.1.4:
dependencies: dependencies:
cheerio: 1.0.0-rc.12 cheerio: 1.0.0-rc.12
@ -2488,6 +2913,10 @@ snapshots:
get-stream@6.0.1: {} get-stream@6.0.1: {}
get-tsconfig@4.8.1:
dependencies:
resolve-pkg-maps: 1.0.0
glob-parent@5.1.2: glob-parent@5.1.2:
dependencies: dependencies:
is-glob: 4.0.3 is-glob: 4.0.3
@ -2573,11 +3002,11 @@ snapshots:
i18next-fs-backend@2.6.0: {} i18next-fs-backend@2.6.0: {}
i18next@24.0.0(typescript@5.7.2): i18next@24.0.0(typescript@5.7.3):
dependencies: dependencies:
'@babel/runtime': 7.25.6 '@babel/runtime': 7.25.6
optionalDependencies: optionalDependencies:
typescript: 5.7.2 typescript: 5.7.3
iconv-lite@0.6.3: iconv-lite@0.6.3:
dependencies: dependencies:
@ -2636,7 +3065,7 @@ snapshots:
jintr@3.0.2: jintr@3.0.2:
dependencies: dependencies:
acorn: 8.12.1 acorn: 8.14.0
js-yaml@4.1.0: js-yaml@4.1.0:
dependencies: dependencies:
@ -2724,6 +3153,10 @@ snapshots:
dependencies: dependencies:
brace-expansion: 2.0.1 brace-expansion: 2.0.1
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
minimist@1.2.8: {} minimist@1.2.8: {}
minipass@3.3.6: minipass@3.3.6:
@ -2873,7 +3306,7 @@ snapshots:
prettier-eslint@16.3.0: prettier-eslint@16.3.0:
dependencies: 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 common-tags: 1.8.2
dlv: 1.1.3 dlv: 1.1.3
eslint: 8.57.1 eslint: 8.57.1
@ -2883,7 +3316,7 @@ snapshots:
prettier: 3.4.2 prettier: 3.4.2
pretty-format: 29.7.0 pretty-format: 29.7.0
require-relative: 0.8.7 require-relative: 0.8.7
typescript: 5.7.2 typescript: 5.7.3
vue-eslint-parser: 9.4.3(eslint@8.57.1) vue-eslint-parser: 9.4.3(eslint@8.57.1)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2949,6 +3382,8 @@ snapshots:
resolve-from@4.0.0: {} resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
responselike@3.0.0: responselike@3.0.0:
dependencies: dependencies:
lowercase-keys: 3.0.0 lowercase-keys: 3.0.0
@ -3100,23 +3535,34 @@ snapshots:
dependencies: dependencies:
punycode: 2.3.1 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: 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: {} ts-mixer@6.0.4: {}
tslib@2.7.0: {} 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: type-check@0.4.0:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
type-fest@0.20.2: {} 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: undici@5.28.4:
dependencies: dependencies:

View file

@ -1,9 +0,0 @@
export default class ICacheAdapter {
get() {}
set() {}
clear() {}
delete() {}
}

9
src/adapters/cache/ICacheAdapter.ts vendored Normal file
View file

@ -0,0 +1,9 @@
export default class ICacheAdapter {
get(_key: string) {}
set<T>(_key: string, _value: T) {}
clear() {}
delete(_key: string) {}
}

View file

@ -1,16 +1,16 @@
import ICacheAdapter from "./ICacheAdapter.js"; import ICacheAdapter from "./ICacheAdapter.js";
export default class MapCache extends ICacheAdapter { export default class MapCache extends ICacheAdapter {
store = new Map();
constructor() { constructor() {
super(); super();
this.store = new Map();
} }
get(key) { get(key: string) {
return this.store.get(key); return this.store.get(key);
} }
set(key, value) { set<T>(key: string, value: T) {
this.store.set(key, value); this.store.set(key, value);
} }
@ -18,7 +18,7 @@ export default class MapCache extends ICacheAdapter {
this.store.clear(); this.store.clear();
} }
delete(key) { delete(key: string) {
this.store.delete(key); this.store.delete(key);
} }
} }

View file

@ -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");
}
}

View file

@ -0,0 +1,9 @@
export default abstract class IDatabaseAdapter<ModelType, QueryType, UpdateType, OptionsType> {
abstract connect(): Promise<void>;
abstract disconnect(): Promise<void>;
abstract find(_model: ModelType, _query?: QueryType, _options?: OptionsType): Promise<unknown[]>;
abstract findOne(_model: ModelType, _query?: QueryType, _options?: OptionsType): Promise<unknown | null>;
abstract updateOne(_model: ModelType, _filter: QueryType, _update: UpdateType, _options?: OptionsType): Promise<unknown>;
abstract deleteOne(_model: ModelType, _filter: QueryType): Promise<unknown>;
abstract findOneOrCreate(_model: ModelType, _filter: QueryType): Promise<unknown>;
}

View file

@ -1,24 +1,21 @@
import mongoose from "mongoose"; import mongoose, { ConnectOptions, Document, Model, FilterQuery, QueryOptions, UpdateQuery } from "mongoose";
import IDatabaseAdapter from "./IDatabaseAdapter.js"; import IDatabaseAdapter from "./IDatabaseAdapter.js";
import logger from "../../helpers/logger.js"; import logger from "@/helpers/logger.js";
import Cache from "../cache/MapCache.js"; import Cache from "../cache/MapCache.js";
export default class MongooseAdapter extends IDatabaseAdapter { export default class MongooseAdapter extends IDatabaseAdapter<Model<any>, FilterQuery<any>, UpdateQuery<any>, QueryOptions> {
/** cache = new Cache();
* options: ConnectOptions;
* @param {string} uri - database url connect uri: string;
* @param {mongoose.ConnectOptions} options - database connect options
*/ constructor(uri: string, options: ConnectOptions = {}) {
constructor(uri, options = {}) {
super(); super();
if (!uri) { if (!uri) {
throw new Error("MongooseAdapter: URI is required."); throw new Error("MongooseAdapter: URI is required.");
} }
this.uri = uri;
this.options = options; this.options = options;
this.cache = new Cache(); this.uri = uri;
} }
async connect() { async connect() {
@ -32,11 +29,11 @@ export default class MongooseAdapter extends IDatabaseAdapter {
this.cache.clear(); this.cache.clear();
} }
#generateCacheKey(modelName, query, options) { #generateCacheKey(modelName: string, query: {}, options: {}) {
return `${modelName}:${JSON.stringify(query)}:${JSON.stringify(options)}`; return `${modelName}:${JSON.stringify(query)}:${JSON.stringify(options)}`;
} }
async find(model, query = {}, options = {}) { async find<T extends Document>(model: Model<T>, query: FilterQuery<T>, options = {}): Promise<T[]> {
const cacheKey = this.#generateCacheKey(model.modelName, query, options); const cacheKey = this.#generateCacheKey(model.modelName, query, options);
if (this.cache.get(cacheKey)) { if (this.cache.get(cacheKey)) {
return this.cache.get(cacheKey); return this.cache.get(cacheKey);
@ -47,7 +44,7 @@ export default class MongooseAdapter extends IDatabaseAdapter {
return result; return result;
} }
async findOne(model, query = {}, options = {}) { async findOne<T extends Document>(model: Model<T>, query: FilterQuery<T>, options = {}): Promise<T | null> {
const cacheKey = this.#generateCacheKey(model.modelName, query, options); const cacheKey = this.#generateCacheKey(model.modelName, query, options);
if (this.cache.get(cacheKey)) { if (this.cache.get(cacheKey)) {
return this.cache.get(cacheKey); return this.cache.get(cacheKey);
@ -58,15 +55,26 @@ export default class MongooseAdapter extends IDatabaseAdapter {
return result; return result;
} }
async updateOne(model, filter, update, options = {}) { async updateOne<T extends Document>(model: Model<T>, filter: FilterQuery<T>, update: UpdateQuery<T>, options = {}) {
const result = await model.updateOne(filter, update, options).exec(); const result = await model.updateOne(filter, update, options).exec();
this.cache.clear(); this.cache.clear();
return result; return result;
} }
async deleteOne(model, filter) { async deleteOne<T extends Document>(model: Model<T>, filter: FilterQuery<T>) {
const result = await model.deleteOne(filter).exec(); const result = await model.deleteOne(filter).exec();
this.cache.clear(); this.cache.clear();
return result; return result;
} }
async findOneOrCreate<T extends Document>(model: Model<T>, filter: FilterQuery<T>) {
this.cache.clear();
const result = await model.findOne(filter).then(result => {
if (result) return result;
return model.create(filter);
});
return result;
}
} }

View file

@ -1,6 +1,7 @@
import path from "node:path"; import path from "node:path";
import { GatewayIntentBits } from "discord.js"; import { GatewayIntentBits, MessageMentionOptions } from "discord.js";
import { AsyncLocalStorage } from "node:async_hooks"; import { AsyncLocalStorage } from "node:async_hooks";
import { ExtendedClient } from "@/structures/client.js";
export const PROJECT_ROOT = path.join(import.meta.dirname, ".."); export const PROJECT_ROOT = path.join(import.meta.dirname, "..");
export const CONFIG_PATH = path.join(PROJECT_ROOT, "..", "config.json"); export const CONFIG_PATH = path.join(PROJECT_ROOT, "..", "config.json");
@ -21,6 +22,6 @@ export const CLIENT_INTENTS = [
GatewayIntentBits.DirectMessages, GatewayIntentBits.DirectMessages,
GatewayIntentBits.DirectMessageReactions, 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<ExtendedClient>();

View file

@ -1,18 +1,15 @@
import logger from "../helpers/logger.js"; import logger from "../helpers/logger.js";
import { resolve } from "node:path"; import { resolve } from "node:path";
import loadCronTasks from "../utils/loadCronTasks.js"; import loadCronTasks from "@/utils/loadCronTasks.js";
import { CronManager } from "../services/cron/index.js"; import { CronManager } from "@/services/cron/index.js";
import { ExtendedClient } from "@/structures/client.js";
export const data = { export const data = {
name: "ready", name: "ready",
once: true, once: true,
}; };
/** export async function run(client: ExtendedClient) {
*
* @param {import("../structures/client.js").ExtendedClient} client
*/
export async function run(client) {
logger.ready(client.user.tag + " is online!"); logger.ready(client.user.tag + " is online!");
// Fetching all app emojis, because we need to use them // Fetching all app emojis, because we need to use them

View file

@ -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 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)); 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 devOnlyCommands = commands.filter(cmd => cmd.options?.devOnly);
const globalCommands = 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<string[]>("devGuildsIds");
await registerGlobalCommands(client, globalCommands); await registerGlobalCommands(client, globalCommands);
await registerDevCommands(client, devOnlyCommands, devGuildsIds); await registerDevCommands(client, devOnlyCommands, devGuildsIds);
}; };
/** const registerGlobalCommands = async (client: ExtendedClient, commands: CommandFileObject[]) => {
* const appCommandsManager = client.application!.commands;
* @param {import("../../../structures/client.js").ExtendedClient} client
* @param {*} commands
*/
const registerGlobalCommands = async (client, commands) => {
const appCommandsManager = client.application.commands;
await appCommandsManager.fetch(); await appCommandsManager.fetch();
await Promise.all( await Promise.all(
@ -29,7 +32,7 @@ const registerGlobalCommands = async (client, commands) => {
const targetCommand = appCommandsManager.cache.find(cmd => cmd.name === data.name); const targetCommand = appCommandsManager.cache.find(cmd => cmd.name === data.name);
if (targetCommand && differentCommands(targetCommand, data)) { 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<ApplicationCommandData>).catch(() => logger.error(`Failed to update command: ${data.name} globally`));
logger.log(`Edited command globally: ${data.name}`); logger.log(`Edited command globally: ${data.name}`);
} else if (!targetCommand) { } else if (!targetCommand) {
@ -38,15 +41,10 @@ const registerGlobalCommands = async (client, commands) => {
}), }),
); );
logger.log("Registered global commands"); logger.log(`Registered ${commands.length} global commands`);
}; };
/** const registerDevCommands = async (client: ExtendedClient, commands: CommandFileObject[], guildsIds: string[]) => {
*
* @param {import("../../../structures/client.js").ExtendedClient} client
* @param {*} commands
*/
const registerDevCommands = async (client, commands, guildsIds) => {
const devGuilds = []; const devGuilds = [];
for (const guildId of guildsIds) { for (const guildId of guildsIds) {
@ -60,7 +58,7 @@ const registerDevCommands = async (client, commands, guildsIds) => {
devGuilds.push(guild); devGuilds.push(guild);
} }
const guildCommandsManagers = []; const guildCommandsManagers: GuildApplicationCommandManager[] = [];
for (const guild of devGuilds) { for (const guild of devGuilds) {
const guildCommandsManager = guild.commands; const guildCommandsManager = guild.commands;
@ -74,7 +72,7 @@ const registerDevCommands = async (client, commands, guildsIds) => {
guildCommandsManagers.map(async guildCommands => { guildCommandsManagers.map(async guildCommands => {
const targetCommand = guildCommands.cache.find(cmd => cmd.name === data.name); const targetCommand = guildCommands.cache.find(cmd => cmd.name === data.name);
if (targetCommand && differentCommands(targetCommand, data)) { 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<ApplicationCommandData>).catch(() => logger.error(`Failed to update command: ${data.name} in ${guildCommands.guild.name} server`));
logger.log(`Edited command globally: ${data.name}`); logger.log(`Edited command globally: ${data.name}`);
} else if (!targetCommand) { } else if (!targetCommand) {

View file

@ -1,16 +1,16 @@
import { resolve } from "node:path"; import { resolve } from "node:path";
import logger from "../../helpers/logger.js"; import logger from "@/helpers/logger.js";
import { getFilePaths } from "../../utils/index.js"; import { getFilePaths } from "@/utils/get-path.js";
import { toFileURL } from "../../utils/resolve-file.js"; import { toFileURL } from "@/utils/resolve-file.js";
import registerCommands from "./functions/registerCommands.js"; import registerCommands from "./functions/registerCommands.js";
import { ExtendedClient } from "@/structures/client.js";
import { CommandFileObject } from "@//types.js";
export class CommandHandler { export class CommandHandler {
constructor(client) { client: ExtendedClient;
/** commands: CommandFileObject[] = [];
* @type {import("../../structures/client.js").ExtendedClient} client constructor(client: ExtendedClient) {
*/
this.client = client; this.client = client;
this.commands = [];
} }
async init() { async init() {
@ -26,7 +26,7 @@ export class CommandHandler {
async #buildCommands() { async #buildCommands() {
const cmdPath = resolve(this.client.configService.get("paths.commands")); 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) { for (const cmdFilePath of commandFilePaths) {
const { data, run } = await import(toFileURL(cmdFilePath)); const { data, run } = await import(toFileURL(cmdFilePath));
@ -58,7 +58,7 @@ export class CommandHandler {
// Skip if autocomplete handler is not defined // Skip if autocomplete handler is not defined
if (isAutocomplete && !targetCommand.autocompleteRun) return; if (isAutocomplete && !targetCommand.autocompleteRun) return;
const command = targetCommand[isAutocomplete ? "autocompleteRun" : "run"]; const command = targetCommand[isAutocomplete ? "autocompleteRun" : "run"]!;
try { try {
await command({ await command({

View file

@ -1,4 +1,4 @@
export default function differentCommands(appCommand, localCommand) { export default function differentCommands(appCommand: any, localCommand: any) {
const appOptions = appCommand.options || []; const appOptions = appCommand.options || [];
const localOptions = localCommand.options || []; const localOptions = localCommand.options || [];
const appDescription = appCommand.description || ""; const appDescription = appCommand.description || "";

View file

@ -1,12 +1,24 @@
import { resolve } from "node:path"; import { resolve } from "node:path";
import logger from "../../helpers/logger.js"; import logger from "@/helpers/logger.js";
import { getFilePaths } from "../../utils/index.js"; import { getFilePaths } from "@/utils/get-path.js";
import { toFileURL } from "../../utils/resolve-file.js"; import { toFileURL } from "@/utils/resolve-file.js";
import { useMainPlayer } from "discord-player"; 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 { export class EventHandler {
constructor(client) { events: EventHandlerEvents[] = [];
this.events = []; client: ExtendedClient;
constructor(client: ExtendedClient) {
this.client = client; this.client = client;
} }
@ -18,7 +30,7 @@ export class EventHandler {
async #buildEvents() { async #buildEvents() {
try { try {
const eventPath = resolve(this.client.configService.get("paths.events")); 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) { for (const eventFilePath of eventFilePaths) {
const { data, run } = await import(toFileURL(eventFilePath)); const { data, run } = await import(toFileURL(eventFilePath));
@ -42,11 +54,9 @@ export class EventHandler {
} }
$registerEvents() { $registerEvents() {
const player = useMainPlayer();
for (const { data, run } of this.events) { for (const { data, run } of this.events) {
if (data.player) player.events.on(data.name, run); if (data.once) this.client.once(data.name, (...args) => run(...args));
if (data.once) this.client.once(data.name, run); else this.client.on(data.name, (...args) => run(...args));
else this.client.on(data.name, run);
} }
} }
} }

View file

@ -1,14 +1,16 @@
import { ExtendedClient } from "@/structures/client.js";
import { CommandHandler } from "./command-handler/index.js"; import { CommandHandler } from "./command-handler/index.js";
import { EventHandler } from "./event-handler/index.js"; import { EventHandler } from "./event-handler/index.js";
export class Handlers { export class Handlers {
constructor(client) { client: ExtendedClient;
constructor(client: ExtendedClient) {
this.client = client; this.client = client;
this.#init(); this.init();
} }
async #init() { private async init() {
const eventHandler = new EventHandler(this.client); const eventHandler = new EventHandler(this.client);
await eventHandler.init(); await eventHandler.init();

View file

@ -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);
}

View file

@ -1,43 +1,26 @@
import { BaseInteraction } from "discord.js"; import { BaseInteraction, User } from "discord.js";
import useClient from "../utils/use-client.js"; import useClient from "@/utils/use-client.js";
export const getLocale = guildId => { export const getLocale = async (guildId: string) => {
const client = useClient(); const client = useClient();
const guild = client.getGuildData(guildId); const guild = await client.getGuildData(guildId);
return guild.language; return guild.language;
}; };
const getAppEmojis = () => { const getAppEmojis = () => {
const client = useClient(); const client = useClient();
return client.application.emojis.cache; return client.application?.emojis.cache;
}; };
/** const formatReply = (prefixEmoji: string, message: string) => {
* const emojis = getAppEmojis()!;
* @param {import('../structures/client.js').ExtendedClient} client
* @param {string} prefixEmoji
* @param {string} message
*/
const formatReply = (prefixEmoji, message) => {
const emojis = getAppEmojis();
return prefixEmoji ? `${emojis.find(e => e.name === prefixEmoji).toString()} ${message}` : `${message}`; 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);
/** export const getUsername = (user: 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 replyTranslated = async (context, key, args, options = {}) => { export const replyTranslated = async (context, key, args, options = {}) => {
const client = useClient(); const client = useClient();
const locale = options.locale || client.configService.get("defaultLang"); const locale = options.locale || client.configService.get("defaultLang");

View file

@ -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<void>} callback - The async callback function to execute for each item in the collection.
* @returns {Promise<void>} 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;
}

58
src/helpers/functions.ts Normal file
View file

@ -0,0 +1,58 @@
export async function asyncForEach<T>(collection: T[], callback: (_item: T) => Promise<void>) {
const allPromises = collection.map(async key => {
await callback(key);
});
return await Promise.all(allPromises);
}
export function sortByKey<T>(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<T>(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;
}

View file

@ -1,6 +1,6 @@
import chalk from "chalk"; import chalk from "chalk";
function format(tDate) { function format(tDate: Date | number) {
return new Intl.DateTimeFormat("ru-RU", { return new Intl.DateTimeFormat("ru-RU", {
year: "numeric", year: "numeric",
month: "2-digit", month: "2-digit",
@ -21,27 +21,27 @@ const logLevels = {
}; };
export default { export default {
log(...content) { log(...content: unknown[]) {
return console.log(`[${format(Date.now())}]: ${logLevels.LOG} ${content.join(" ")}`); 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(" ")}`); 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(" ")}`); 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(" ")}`); 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(" ")}`); 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(" ")}`); return console.log(`[${format(Date.now())}]: ${logLevels.READY} ${content.join(" ")}`);
}, },
}; };

View file

@ -1,6 +1,6 @@
import useClient from "../../utils/use-client.js"; import useClient from "@/utils/use-client.js";
import UserModel from "../../models/UserModel.js"; import UserModel from "@/models/UserModel.js";
import { createEmbed } from "../../utils/create-embed.js"; import { createEmbed } from "@/utils/create-embed.js";
import logger from "../logger.js"; import logger from "../logger.js";
import { getNoun } from "../functions.js"; import { getNoun } from "../functions.js";
@ -10,7 +10,9 @@ export const data = {
const client = useClient(); const client = useClient();
const guilds = client.guilds.cache.values(); 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 currentData = new Date();
const currentYear = currentData.getFullYear(); const currentYear = currentData.getFullYear();
@ -24,11 +26,15 @@ export const data = {
if (!channel) return; if (!channel) return;
if (!channel.isSendable()) return;
const userIDs = users.filter(u => guild.members.cache.has(u.id)).map(u => u.id); const userIDs = users.filter(u => guild.members.cache.has(u.id)).map(u => u.id);
await Promise.all( await Promise.all(
userIDs.map(async userID => { userIDs.map(async userID => {
const user = users.find(u => u.id === 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 userData = new Date(user.birthdate).getFullYear() <= 1970 ? new Date(user.birthdate * 1000) : new Date(user.birthdate);
const userYear = userData.getFullYear(); const userYear = userData.getFullYear();
const userMonth = userData.getMonth(); const userMonth = userData.getMonth();
@ -38,7 +44,9 @@ export const data = {
if (userDate === currentDate && userMonth === currentMonth) { if (userDate === currentDate && userMonth === currentMonth) {
const embed = createEmbed({ const embed = createEmbed({
author: client.user.username, author: {
name: client.user.username,
},
fields: [ fields: [
{ {
name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", { name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", {
@ -47,11 +55,7 @@ export const data = {
value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", { value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", {
lng: data.language, lng: data.language,
user: user.id, user: user.id,
age: `**${age}** ${getNoun(age, [ 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)])}`,
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),
])}`,
}), }),
}, },
], ],

View file

@ -1,8 +1,9 @@
import { createEmbed } from "@/utils/create-embed.js";
import UserModel from "../../models/UserModel.js"; import UserModel from "../../models/UserModel.js";
import useClient from "../../utils/use-client.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", name: "checkReminds",
task: async () => { task: async () => {
const client = useClient(); const client = useClient();
@ -11,17 +12,19 @@ export const data = {
for (const user of users) { for (const user of users) {
if (!client.users.cache.has(user.id)) client.users.fetch(user.id); 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 => { client.cacheReminds.forEach(async ({ id, reminds }) => {
const cachedUser = client.users.cache.get(user.id); const cachedUser = client.users.cache.get(id);
if (!cachedUser) return; if (!cachedUser) return;
const reminds = user.reminds, const mustSent = reminds.filter(r => r.sendAt < Math.floor(Date.now() / 1000));
mustSent = reminds.filter(r => r.sendAt < Math.floor(Date.now() / 1000));
if (!mustSent.length) return; if (!mustSent.length) return;
@ -53,15 +56,23 @@ export const data = {
embeds: [embed], embeds: [embed],
}) })
.then(() => { .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 (!reminds.length) client.cacheReminds.delete(id);
if (!user.reminds.length) client.cacheReminds.delete(user.id);
}); });
}, },
schedule: "* * * * * *", schedule: "* * * * * *",

View file

@ -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,
},
},
}),
);

68
src/models/GuildModel.ts Normal file
View file

@ -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<IGuildSchema>({
id: { type: String },
membersData: { type: Object, default: {} },
members: [{ type: Schema.Types.ObjectId, ref: "Member" }],
language: { type: String, default: client.configService.get<string>("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<IGuildSchema>("Guild", GuildSchema);

View file

@ -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,
},
},
}),
);

57
src/models/MemberModel.ts Normal file
View file

@ -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<IMemberSchema>({
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<IMemberSchema>("Member", memberSchema);

View file

@ -1,6 +1,37 @@
import { Schema, model } from "mongoose"; import { Schema, model, Document } from "mongoose";
import { createCanvas, loadImage } from "@napi-rs/canvas"; 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 = () => { const genToken = () => {
let token = ""; let token = "";
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzy0123456789.-_"; const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzy0123456789.-_";
@ -9,7 +40,7 @@ const genToken = () => {
return token; return token;
}; };
const userSchema = new Schema({ const userSchema = new Schema<IUserSchema>({
id: { type: String }, id: { type: String },
rep: { type: Number, default: 0 }, rep: { type: Number, default: 0 },
@ -82,7 +113,16 @@ const userSchema = new Schema({
}, },
afk: { type: String, default: null }, afk: { type: String, default: null },
reminds: { type: Array, default: [] }, reminds: [
{
type: Object,
default: {
message: null,
createdAt: null,
sendAt: null,
},
},
],
logged: { type: Boolean, default: false }, logged: { type: Boolean, default: false },
apiToken: { type: String, default: genToken() }, apiToken: { type: String, default: genToken() },
}); });
@ -110,4 +150,4 @@ userSchema.method("getAchievements", async function () {
return await canvas.encode("png"); return await canvas.encode("png");
}); });
export default model("User", userSchema); export default model<IUserSchema>("User", userSchema);

View file

@ -1,28 +1,16 @@
import fs from "fs"; import fs from "fs";
import { CONFIG_PATH } from "../../constants/index.js"; import { CONFIG_PATH } from "@/constants/index.js";
import logger from "../../helpers/logger.js"; import logger from "@/helpers/logger.js";
class ConfigService { class ConfigService {
constructor() { config = this.loadConfig();
this.config = this.#loadConfig();
}
/** get<T>(key: string): T {
*
* @param {string} key - key of the config
* @returns {*} - value of the config
*/
get(key) {
const keys = key.split("."); const keys = key.split(".");
return keys.reduce((config, k) => (config && config[k] !== undefined ? config[k] : undefined), this.config); return keys.reduce((config, k) => (config && config[k] !== undefined ? config[k] : undefined), this.config);
} }
/** set(key: string, value: unknown) {
* Set a config value.
* @param {string} key - key of the config to set
* @param {*} value - value to set
*/
set(key, value) {
const keys = key.split("."); const keys = key.split(".");
keys.reduce((config, k, i) => { keys.reduce((config, k, i) => {
if (i === keys.length - 1) { if (i === keys.length - 1) {
@ -32,14 +20,10 @@ class ConfigService {
} }
return config[k]; return config[k];
}, this.config); }, this.config);
this.#saveConfig(); this.saveConfig();
} }
/** private loadConfig() {
* Load the config from the file.
* @returns {Config} - loaded config
*/
#loadConfig() {
if (fs.existsSync(CONFIG_PATH)) { if (fs.existsSync(CONFIG_PATH)) {
return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")); return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
} else { } else {
@ -48,10 +32,7 @@ class ConfigService {
} }
} }
/** private saveConfig() {
* Save the config to the file.
*/
#saveConfig() {
try { try {
fs.writeFileSync(CONFIG_PATH, JSON.stringify(this.config, null, 4), "utf-8"); fs.writeFileSync(CONFIG_PATH, JSON.stringify(this.config, null, 4), "utf-8");
} catch (e) { } catch (e) {

View file

@ -1,10 +1,12 @@
import { CronJob } from "cron"; import { CronJob } from "cron";
import logger from "../../helpers/logger.js"; import logger from "../../helpers/logger.js";
import { CronTaskData } from "@/types.js";
export class CronManager { export class CronManager {
constructor(tasks) { jobs = new Map<string, { job: CronJob; isRunning: boolean; isError: boolean }>();
tasks: CronTaskData[];
constructor(tasks: CronTaskData[]) {
this.tasks = tasks; this.tasks = tasks;
this.jobs = new Map();
} }
async init() { async init() {
@ -31,7 +33,7 @@ export class CronManager {
await task.task(); await task.task();
} catch (error) { } catch (error) {
logger.error(`Error executing cron task "${task.name}":`, 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, null,
@ -50,9 +52,8 @@ export class CronManager {
} }
stopAll() { 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()) { for (const [_, jobInfo] of this.jobs.entries()) {
jobInfo.job.stop(); jobInfo.job.stop();
} }

View file

@ -1,40 +1,50 @@
import i18next from "i18next"; import i18next, { TOptionsBase } from "i18next";
import Backend from "i18next-fs-backend"; import Backend from "i18next-fs-backend";
import fs from "fs/promises"; import fs from "fs/promises";
import { resolve, join } from "path"; import { resolve, join } from "path";
import logger from "../../helpers/logger.js"; import logger from "@/helpers/logger.js";
import supportedLanguages from "./language-meta.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 { export default class InternationalizationService {
/** private client: ExtendedClient;
* Constructs an instance of the InternationalizationService. private options: {
* localesPath: string;
* @param {import("../../structures/client").ExtendedClient} client - The client instance. defaultLanguage: string;
* @param {Object} [options={}] - Optional configuration options. };
*/
constructor(client, options = {}) { constructor(client: ExtendedClient, options: InternationalizationServiceOptions = {}) {
this.client = client; this.client = client;
this.options = { this.options = {
localesPath: resolve(this.client.configService.get("paths.locales")), localesPath: resolve(this.client.configService.get("paths.locales")),
defaultLanguage: options.defaultLanguage || "en-US", defaultLanguage: options.defaultLanguage || "en-US",
}; };
this.i18next = this.#init(); this.init();
} }
get getSupportedLanguages() { get getSupportedLanguages(): string[] {
return supportedLanguages.map(lang => lang.locale); return supportedLanguages.map(lang => lang.locale);
} }
async #walkDirectory(dir, namespaces = [], folderName = "") { private async walkDirectory(dir: string, namespaces: string[] = [], folderName: string = ""): Promise<WalkDirectoryResult> {
const files = await fs.readdir(dir, { withFileTypes: true }); const files = await fs.readdir(dir, { withFileTypes: true });
const languages = []; const languages: string[] = [];
for (const file of files) { for (const file of files) {
if (file.isDirectory()) { if (file.isDirectory()) {
const isLanguage = file.name.includes("-"); const isLanguage = file.name.includes("-");
if (isLanguage) languages.push(file.name); 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; namespaces = folder.namespaces;
} else { } else {
@ -44,10 +54,10 @@ export default class InternationalizationService {
return { namespaces: [...new Set(namespaces)], languages }; return { namespaces: [...new Set(namespaces)], languages };
} }
async #init() { private async init() {
const { namespaces, languages } = await this.#walkDirectory(this.options.localesPath); const { namespaces, languages } = await this.walkDirectory(this.options.localesPath);
const i18n = await i18next.use(Backend).init({ const i18nInstance = await i18next.use(Backend).init({
backend: { backend: {
loadPath: resolve(this.options.localesPath, "./{{lng}}/{{ns}}.json"), loadPath: resolve(this.options.localesPath, "./{{lng}}/{{ns}}.json"),
}, },
@ -61,13 +71,14 @@ export default class InternationalizationService {
initImmediate: false, initImmediate: false,
}); });
this.client.translate = (key, options = {}) => { // Типизированная функция translate
this.client.translate = (key: string, options: TOptionsBase = {}): string => {
const lng = options.lng || this.options.defaultLanguage; const lng = options.lng || this.options.defaultLanguage;
return i18next.t(key, { lng, ...options }); return i18next.t(key, { lng, ...options });
}; };
logger.log("Internationalization initialized"); logger.log("Internationalization initialized");
return i18n; return i18nInstance;
} }
} }

View file

@ -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<GuildModel>} 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<UserModel>} 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<MemberModel>} 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;
}
}

69
src/structures/client.ts Normal file
View file

@ -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<true> {
configService = new ConfigService();
adapter = new MongooseAdapter(this.configService.get("mongoDB"));
cacheReminds = new Map<string, cacheRemindsData>();
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;
}
}

53
src/types.d.ts vendored Normal file
View file

@ -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> | 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<CacheType>;
client: ExtendedClient;
}
export interface CommandOptions {
devOnly?: boolean;
userPermissions?: PermissionsString | PermissionsString[];
botPermissions?: PermissionsString | PermissionsString[];
}
export interface CommandFileObject {
data: CommandData;
options?: CommandOptions;
run: <Cached extends CacheType = CacheType>(_ctx: CommandContext<Interaction, Cached>) => Awaited<void>;
autocompleteRun?: <Cached extends CacheType = CacheType>(_ctx: CommandContext<Interaction, Cached>) => Awaited<void>;
}

View file

@ -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"));
};

10
src/utils/create-embed.ts Normal file
View file

@ -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"));
};

View file

@ -1,11 +1,9 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path"; 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) => { export const getFilePaths = async (directory: string, nesting: boolean) => {
let filePaths = []; let filePaths: string[] = [];
if (!directory) return;
const absoluteDirectory = path.isAbsolute(directory) ? directory : path.join(PROJECT_ROOT, directory); 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()) { if (nesting && file.isDirectory()) {
filePaths = [...filePaths, ...(await getFilePaths(filePath, true))]; const nestedFiles = await getFilePaths(filePath, true);
filePaths = [...filePaths, ...nestedFiles];
} }
} }

View file

@ -1,3 +0,0 @@
export * from "./create-embed.js";
export * from "./get-path.js";
export * from "./resolve-file.js";

View file

@ -1,15 +1,16 @@
import { CronTaskData } from "@/types.js";
import logger from "../helpers/logger.js"; import logger from "../helpers/logger.js";
import { getFilePaths } from "./get-path.js"; import { getFilePaths } from "./get-path.js";
import { toFileURL } from "./resolve-file.js"; import { toFileURL } from "./resolve-file.js";
const loadCronTasks = async taskPath => { const loadCronTasks = async (taskPath: string): Promise<CronTaskData[]> => {
try { 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 = []; const tasks = [];
for (const filePath of filePaths) { for (const filePath of filePaths) {
const { data } = await import(toFileURL(filePath)); const { data } = (await import(toFileURL(filePath))) as { data: CronTaskData };
if (!data) continue; if (!data) continue;

View file

@ -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, "/");
};

View file

@ -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, "/");
};

View file

@ -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;
}

9
src/utils/use-client.ts Normal file
View file

@ -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;
}

24
tsconfig.json Normal file
View file

@ -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"] // Исключаемые файлы
}