diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0176dee..ee17e2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,16 +1,17 @@ on: [push] -name: Run Tests +name: Run Tests & Lint concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: tests: - name: Run Tests + name: Run Tests & Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install + - run: bun lint:check - run: bun test diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 275ace5..0000000 --- a/.prettierrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tabWidth": 2, - "singleQuote": false, - "quoteProps": "preserve", - "semi": false, - "printWidth": 100, - "useTabs": false, - "endOfLine": "lf", - "arrowParens": "always", - "trailingComma": "all" -} diff --git a/bun.lock b/bun.lock index 23362c6..92cbb8a 100644 --- a/bun.lock +++ b/bun.lock @@ -19,8 +19,13 @@ }, "devDependencies": { "@hey-api/openapi-ts": "^0.78.1", + "@richardscull/eslint-config": "^1.0.6", "@types/bun": "latest", "dotenv-cli": "^8.0.0", + "eslint": "^9.39.2", + "jiti": "^2.6.1", + "lint-staged": "^16.2.7", + "simple-git-hooks": "^2.13.1", }, "peerDependencies": { "typescript": "^5", @@ -28,6 +33,40 @@ }, }, "packages": { + "@altano/repository-tools": ["@altano/repository-tools@2.0.1", "", {}, "sha512-YE/52CkFtb+YtHPgbWPai7oo5N9AKnMuP5LM+i2AG7G1H2jdYBCO1iDnkDE3dZ3C1MIgckaF+d5PNRulgt0bdw=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], + + "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], + + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], + + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + "@discordjs/builders": ["@discordjs/builders@1.11.2", "", { "dependencies": { "@discordjs/formatters": "^0.6.1", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.1", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A=="], "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], @@ -40,14 +79,62 @@ "@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="], + "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + "@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@es-joy/jsdoccomment": ["@es-joy/jsdoccomment@0.78.0", "", { "dependencies": { "@types/estree": "^1.0.8", "@typescript-eslint/types": "^8.46.4", "comment-parser": "1.4.1", "esquery": "^1.6.0", "jsdoc-type-pratt-parser": "~7.0.0" } }, "sha512-rQkU5u8hNAq2NVRzHnIUUvR6arbO0b6AOlvpTNS48CkiKSn/xtNfOzBK23JE4SiW89DgvU7GtxLVgV4Vn2HBAw=="], + + "@eslint-community/eslint-plugin-eslint-comments": ["@eslint-community/eslint-plugin-eslint-comments@4.5.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "ignore": "^5.2.4" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint-react/ast": ["@eslint-react/ast@2.4.0", "", { "dependencies": { "@eslint-react/eff": "2.4.0", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/typescript-estree": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "string-ts": "^2.3.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-xi/uVi4/jaqPDgF9tO4laLAAZLBrKXearHKIAJWmnY+ymu0LBjX8VaLuf6GuUq7ryek5NO2kOZDYNx4C3qV4iw=="], + + "@eslint-react/core": ["@eslint-react/core@2.4.0", "", { "dependencies": { "@eslint-react/ast": "2.4.0", "@eslint-react/eff": "2.4.0", "@eslint-react/shared": "2.4.0", "@eslint-react/var": "2.4.0", "@typescript-eslint/scope-manager": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "birecord": "^0.1.1", "ts-pattern": "^5.9.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Ibt8NlFhT+FffNKtZmb2xamHEFzwK0AzmmOUGVm/B49B2ShOOR3kQg7ZaVNUR3By0Q0hPlIYnefKH2KgaUJ7jA=="], + + "@eslint-react/eff": ["@eslint-react/eff@2.4.0", "", {}, "sha512-iWB2IaO+ygt8YPGXqUIg3KQmu3GgKecwbHrz0nasEO2BuhR7rAPaBcqnC3s8NvMUicJ/q03yWzfTgMuFST5+jg=="], + + "@eslint-react/eslint-plugin": ["@eslint-react/eslint-plugin@2.4.0", "", { "dependencies": { "@eslint-react/eff": "2.4.0", "@eslint-react/shared": "2.4.0", "@typescript-eslint/scope-manager": "^8.50.1", "@typescript-eslint/type-utils": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "eslint-plugin-react-dom": "2.4.0", "eslint-plugin-react-hooks-extra": "2.4.0", "eslint-plugin-react-naming-convention": "2.4.0", "eslint-plugin-react-web-api": "2.4.0", "eslint-plugin-react-x": "2.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-+d3JGOc+EM80LGO7Ynz8vRRex+xA+ilM0/BDvwHTlvfdNK6GeH8EV7RewAClvGijfkEMxCoehglVRGwjzmoKbw=="], + + "@eslint-react/shared": ["@eslint-react/shared@2.4.0", "", { "dependencies": { "@eslint-react/eff": "2.4.0", "@typescript-eslint/utils": "^8.50.1", "ts-pattern": "^5.9.0", "zod": "^4.2.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1LYGz8AzAN9knt56h2onTL4beTLxys/KzV+PJwODydAqYKIlWAOtJJK1HLwDrXneiLP8G2mHrt2XwcmrXzzaRw=="], + + "@eslint-react/var": ["@eslint-react/var@2.4.0", "", { "dependencies": { "@eslint-react/ast": "2.4.0", "@eslint-react/eff": "2.4.0", "@typescript-eslint/scope-manager": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "ts-pattern": "^5.9.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vIVIQfBS8qfzu/AM4/fAek+Qab63MZwintr7gdSOjRy5z/7Kixjzg5Nj1AeW78jBtPrZSDYUpYb8ZXM0mN/Qag=="], + + "@eslint/compat": ["@eslint/compat@1.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0" }, "peerDependencies": { "eslint": "^8.40 || 9" }, "optionalPeers": ["eslint"] }, "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], + + "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@faker-js/faker": ["@faker-js/faker@9.9.0", "", {}, "sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA=="], "@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.0.6", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0", "lodash": "^4.17.21" } }, "sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w=="], "@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.78.1", "", { "dependencies": { "@hey-api/json-schema-ref-parser": "1.0.6", "ansi-colors": "4.1.3", "c12": "2.0.1", "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8", "open": "10.1.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-DjpA26aqP9JP6tYUlcydNdxC4KbAhjnIDC4aL5V81DnTdQ70SkD9vFDe8CMeLxAHcngr6ca2kIEIJH3iT2KHkA=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], @@ -86,8 +173,36 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], + + "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], + + "@quansync/fs": ["@quansync/fs@1.0.0", "", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], + + "@richardscull/eslint-config": ["@richardscull/eslint-config@1.0.6", "", { "dependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.5.0", "@eslint-react/eslint-plugin": "^2.3.13", "@eslint/js": "^9.39.2", "@stylistic/eslint-plugin": "^5.6.1", "@typescript-eslint/types": "^8.50.0", "@unocss/eslint-config": "^66.5.10", "defu": "^6.1.4", "eslint-config-flat-gitignore": "^2.1.0", "eslint-plugin-antfu": "^3.1.1", "eslint-plugin-command": "^3.4.0", "eslint-plugin-hyoban": "^0.6.1", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-jsonc": "^2.21.0", "eslint-plugin-package-json": "^0.85.0", "eslint-plugin-react-google-translate": "^0.1.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.26", "eslint-plugin-regexp": "^2.10.0", "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-tailwindcss": "4.0.0-beta.0", "eslint-plugin-unicorn": "^62.0.0", "eslint-plugin-unused-imports": "^4.3.0", "globals": "^16.5.0", "jsonc-eslint-parser": "^2.4.2", "local-pkg": "^1.1.2", "read-package-up": "^12.0.0", "typescript-eslint": "^8.50.0" }, "peerDependencies": { "eslint": "^9.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-sOJaUbN6ii7ljgbxIJ9PsLhDmjzWQyYaWPssU7l2Vw5aF75uCjzqiyRWr0G8RBNTxFW5sYiEC5xkIIwZRdDWWA=="], + "@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="], "@sapphire/cron": ["@sapphire/cron@1.2.1", "", { "dependencies": { "@sapphire/utilities": "^3.18.1" } }, "sha512-K96GX4UkzgC/Y2VHXVjhM2Bl4D04552nr/fDiOj9bOACW1+wqeFfLJ3eV6jleTSXmzPIwDvkPIUJLf8A5KSD+w=="], @@ -130,24 +245,124 @@ "@sapphire/utilities": ["@sapphire/utilities@3.18.2", "", {}, "sha512-QGLdC9+pT74Zd7aaObqn0EUfq40c4dyTL65pFnkM6WO1QYN7Yg/s4CdH+CXmx0Zcu6wcfCWILSftXPMosJHP5A=="], + "@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.6.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.0", "@typescript-eslint/types": "^8.47.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/node": ["@types/node@24.0.10", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA=="], + "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], + "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.50.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.50.1", "@typescript-eslint/type-utils": "8.50.1", "@typescript-eslint/utils": "8.50.1", "@typescript-eslint/visitor-keys": "8.50.1", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.50.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.50.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.50.1", "@typescript-eslint/types": "8.50.1", "@typescript-eslint/typescript-estree": "8.50.1", "@typescript-eslint/visitor-keys": "8.50.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.50.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.50.1", "", { "dependencies": { "@typescript-eslint/types": "8.50.1", "@typescript-eslint/visitor-keys": "8.50.1" } }, "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.50.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.50.1", "", { "dependencies": { "@typescript-eslint/types": "8.50.1", "@typescript-eslint/typescript-estree": "8.50.1", "@typescript-eslint/utils": "8.50.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.50.1", "", {}, "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.50.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.50.1", "@typescript-eslint/tsconfig-utils": "8.50.1", "@typescript-eslint/types": "8.50.1", "@typescript-eslint/visitor-keys": "8.50.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.50.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.50.1", "@typescript-eslint/types": "8.50.1", "@typescript-eslint/typescript-estree": "8.50.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.50.1", "", { "dependencies": { "@typescript-eslint/types": "8.50.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ=="], + + "@unocss/config": ["@unocss/config@66.5.10", "", { "dependencies": { "@unocss/core": "66.5.10", "unconfig": "^7.4.1" } }, "sha512-udBhfMe+2MU70ZdjnRLnwLQ+0EHYJ4f5JjjvHsfmQ0If4KeYmSStWBuX+/LHNQidhl487JiwW1lBDQ8pKHmbiw=="], + + "@unocss/core": ["@unocss/core@66.5.10", "", {}, "sha512-SEmPE4pWNn9VcCvZqovPwFGuG/j69W3zh+x1Ky4z/I2pnyoB0Y0lBmq22KVu/dwExe+ZKKTQpxa0j5rbE27rDQ=="], + + "@unocss/eslint-config": ["@unocss/eslint-config@66.5.10", "", { "dependencies": { "@unocss/eslint-plugin": "66.5.10" } }, "sha512-kDoXTBZcI7RCdWPekKrjgiuRcNYfdwjEkG6HtS1++jM0LhK6QgaMfu4p+4j0gfAz86ZNotghM3u8aWO6Fu0nRA=="], + + "@unocss/eslint-plugin": ["@unocss/eslint-plugin@66.5.10", "", { "dependencies": { "@typescript-eslint/utils": "^8.46.4", "@unocss/config": "66.5.10", "@unocss/core": "66.5.10", "@unocss/rule-utils": "66.5.10", "magic-string": "^0.30.21", "synckit": "^0.11.11" } }, "sha512-Fzvl5ISMoGnALo9tqI15nNNWZza2ICqmzyujQCyzsxDZEVZzajNvt8wACVHoEz+dUZykjMPJqqdmX5ZijcPZ1w=="], + + "@unocss/rule-utils": ["@unocss/rule-utils@66.5.10", "", { "dependencies": { "@unocss/core": "^66.5.10", "magic-string": "^0.30.21" } }, "sha512-497GPWZpArNG25cto0Yq3/Yw+i0x7/N/ySq1HHeE3lB43sdmCv6+m6QEv14I/9/e5WJhQOmrY5LmHZYXC7xxMw=="], + + "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="], + + "@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.11.1", "", { "os": "android", "cpu": "arm64" }, "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g=="], + + "@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g=="], + + "@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ=="], + + "@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.11.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw=="], + + "@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw=="], + + "@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw=="], + + "@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ=="], + + "@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w=="], + + "@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.11.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA=="], + + "@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ=="], + + "@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew=="], + + "@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.11.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg=="], + + "@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w=="], + + "@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA=="], + + "@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.11.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, "cpu": "none" }, "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ=="], + + "@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw=="], + + "@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ=="], + + "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + "ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="], + + "birecord": ["birecord@0.1.1", "", {}, "sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + + "builtin-modules": ["builtin-modules@5.0.0", "", {}, "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg=="], + "bun-sqlite-migrations": ["bun-sqlite-migrations@1.0.2", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-WLw8q67KM+1RN7o4DqVVhmJASypuBp8fygrfA8QD5HZEjiP+E5hD1SV2dpyB7A4tFqLdUF8cdln7+Ptj5+Hz1Q=="], "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], @@ -156,12 +371,30 @@ "c12": ["c12@2.0.1", "", { "dependencies": { "chokidar": "^4.0.1", "confbox": "^0.1.7", "defu": "^6.1.4", "dotenv": "^16.4.5", "giget": "^1.2.3", "jiti": "^2.3.0", "mlly": "^1.7.1", "ohash": "^1.1.4", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", "pkg-types": "^1.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001761", "", {}, "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + "ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + "clean-regexp": ["clean-regexp@1.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw=="], + + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-truncate": ["cli-truncate@5.1.1", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="], + + "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], @@ -176,14 +409,28 @@ "commander": ["commander@13.0.0", "", {}, "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ=="], + "comment-parser": ["comment-parser@1.4.1", "", {}, "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg=="], + + "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "default-browser": ["default-browser@5.2.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg=="], "default-browser-id": ["default-browser-id@5.0.0", "", {}, "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA=="], @@ -194,8 +441,14 @@ "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + "detect-indent": ["detect-indent@7.0.2", "", {}, "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A=="], + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + "detect-newline": ["detect-newline@4.0.1", "", {}, "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog=="], + + "diff-sequences": ["diff-sequences@27.5.1", "", {}, "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ=="], + "discord-api-types": ["discord-api-types@0.38.15", "", {}, "sha512-RX3skyRH7p6BlHOW62ztdnIc87+wv4TEJEURMir5k5BbRJ10wK1MCqFEO6USHTol3gkiHLE6wWoHhNQ2pqB4AA=="], "discord.js": ["discord.js@14.21.0", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.1", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-U5w41cEmcnSfwKYlLv5RJjB8Joa+QJyRwIJz5i/eg+v2Qvv6EYpCRhN9I2Rlf0900LuqSDg8edakUATrDZQncQ=="], @@ -206,38 +459,234 @@ "dotenv-expand": ["dotenv-expand@10.0.0", "", {}, "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A=="], + "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], + + "eslint-compat-utils": ["eslint-compat-utils@0.6.5", "", { "dependencies": { "semver": "^7.5.4" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ=="], + + "eslint-config-flat-gitignore": ["eslint-config-flat-gitignore@2.1.0", "", { "dependencies": { "@eslint/compat": "^1.2.5" }, "peerDependencies": { "eslint": "^9.5.0" } }, "sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA=="], + + "eslint-fix-utils": ["eslint-fix-utils@0.4.0", "", { "peerDependencies": { "@types/estree": ">=1", "eslint": ">=8" }, "optionalPeers": ["@types/estree"] }, "sha512-nCEciwqByGxsKiWqZjqK7xfL+7dUX9Pi0UL3J0tOwfxVN9e6Y59UxEt1ZYsc3XH0ce6T1WQM/QU2DbKK/6IG7g=="], + + "eslint-import-context": ["eslint-import-context@0.1.9", "", { "dependencies": { "get-tsconfig": "^4.10.1", "stable-hash-x": "^0.2.0" }, "peerDependencies": { "unrs-resolver": "^1.0.0" }, "optionalPeers": ["unrs-resolver"] }, "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg=="], + + "eslint-json-compat-utils": ["eslint-json-compat-utils@0.2.1", "", { "dependencies": { "esquery": "^1.6.0" }, "peerDependencies": { "eslint": "*", "jsonc-eslint-parser": "^2.4.0" } }, "sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg=="], + + "eslint-plugin-antfu": ["eslint-plugin-antfu@3.1.1", "", { "peerDependencies": { "eslint": "*" } }, "sha512-7Q+NhwLfHJFvopI2HBZbSxWXngTwBLKxW1AGXLr2lEGxcEIK/AsDs8pn8fvIizl5aZjBbVbVK5ujmMpBe4Tvdg=="], + + "eslint-plugin-command": ["eslint-plugin-command@3.4.0", "", { "dependencies": { "@es-joy/jsdoccomment": "^0.78.0" }, "peerDependencies": { "eslint": "*" } }, "sha512-EW4eg/a7TKEhG0s5IEti72kh3YOTlnhfFNuctq5WnB1fst37/IHTd5OkD+vnlRf3opTvUcSRihAateP6bT5ZcA=="], + + "eslint-plugin-hyoban": ["eslint-plugin-hyoban@0.6.1", "", { "peerDependencies": { "eslint": "*" } }, "sha512-DJI5rCIATcK2e4f7TMt1+sdMSXEAytcn469dLV0hSn4lVvVXsT6uLH/Pogj/cm+m6I8AuUWoHGj/OId5M8tLCg=="], + + "eslint-plugin-import-x": ["eslint-plugin-import-x@4.16.1", "", { "dependencies": { "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", "debug": "^4.4.1", "eslint-import-context": "^0.1.9", "is-glob": "^4.0.3", "minimatch": "^9.0.3 || ^10.0.1", "semver": "^7.7.2", "stable-hash-x": "^0.2.0", "unrs-resolver": "^1.9.2" }, "peerDependencies": { "@typescript-eslint/utils": "^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "eslint-import-resolver-node": "*" }, "optionalPeers": ["@typescript-eslint/utils", "eslint-import-resolver-node"] }, "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ=="], + + "eslint-plugin-jsonc": ["eslint-plugin-jsonc@2.21.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.5.1", "diff-sequences": "^27.5.1", "eslint-compat-utils": "^0.6.4", "eslint-json-compat-utils": "^0.2.1", "espree": "^9.6.1 || ^10.3.0", "graphemer": "^1.4.0", "jsonc-eslint-parser": "^2.4.0", "natural-compare": "^1.4.0", "synckit": "^0.6.2 || ^0.7.3 || ^0.11.5" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-HttlxdNG5ly3YjP1cFMP62R4qKLxJURfBZo2gnMY+yQojZxkLyOpY1H1KRTKBmvQeSG9pIpSGEhDjE17vvYosg=="], + + "eslint-plugin-package-json": ["eslint-plugin-package-json@0.85.0", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.0", "package-json-validator": "~0.59.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", "sort-package-json": "^3.4.0", "validate-npm-package-name": "^7.0.0" }, "peerDependencies": { "eslint": ">=8.0.0", "jsonc-eslint-parser": "^2.0.0" } }, "sha512-MrOxFvhbqLuk4FIPG9v3u9Amn0n137J8LKILHvgfxK3rRyAHEVzuZM0CtpXFTx7cx4LzmAzONtlpjbM0UFNuTA=="], + + "eslint-plugin-react-dom": ["eslint-plugin-react-dom@2.4.0", "", { "dependencies": { "@eslint-react/ast": "2.4.0", "@eslint-react/core": "2.4.0", "@eslint-react/eff": "2.4.0", "@eslint-react/shared": "2.4.0", "@eslint-react/var": "2.4.0", "@typescript-eslint/scope-manager": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "compare-versions": "^6.1.1", "string-ts": "^2.3.1", "ts-pattern": "^5.9.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nzBLj2bD2JJuIJlonENAE9Dp8Sy9Gw7Y45Y4mwjJ8PLV6hABP6W/sgeF0NXpzBiyClXRnjoCPRwylY0XjUaR+w=="], + + "eslint-plugin-react-google-translate": ["eslint-plugin-react-google-translate@0.1.1", "", { "dependencies": { "requireindex": "^1.2.0" }, "peerDependencies": { "eslint": ">=7" } }, "sha512-70w3YW211yFSexWLshO+rSVtr6NDuA0qaU/ARVQ9pG0WFia5CyGFhKCed6LKKngdgfQTzjrcWlg7IPJehJwTsg=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], + + "eslint-plugin-react-hooks-extra": ["eslint-plugin-react-hooks-extra@2.4.0", "", { "dependencies": { "@eslint-react/ast": "2.4.0", "@eslint-react/core": "2.4.0", "@eslint-react/eff": "2.4.0", "@eslint-react/shared": "2.4.0", "@eslint-react/var": "2.4.0", "@typescript-eslint/scope-manager": "^8.50.1", "@typescript-eslint/type-utils": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "string-ts": "^2.3.1", "ts-pattern": "^5.9.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-uLOSXhW1+RgXrkwErfnoiGSsAxdtCJyPG8yyswR+OL3bhaT3gwj5HcyEWpj+9GrzvDnb6oknfddpyAl2RmOOHw=="], + + "eslint-plugin-react-naming-convention": ["eslint-plugin-react-naming-convention@2.4.0", "", { "dependencies": { "@eslint-react/ast": "2.4.0", "@eslint-react/core": "2.4.0", "@eslint-react/eff": "2.4.0", "@eslint-react/shared": "2.4.0", "@eslint-react/var": "2.4.0", "@typescript-eslint/scope-manager": "^8.50.1", "@typescript-eslint/type-utils": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "string-ts": "^2.3.1", "ts-pattern": "^5.9.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7kmdrdKVO+54AUtUYzrpGqs9+wRREOWrr1A1DoMItZ8KXPv6TRWlUxm2opFFe2QysV0tSVvb4TVlfWxKcG1eLw=="], + + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.26", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ=="], + + "eslint-plugin-react-web-api": ["eslint-plugin-react-web-api@2.4.0", "", { "dependencies": { "@eslint-react/ast": "2.4.0", "@eslint-react/core": "2.4.0", "@eslint-react/eff": "2.4.0", "@eslint-react/shared": "2.4.0", "@eslint-react/var": "2.4.0", "@typescript-eslint/scope-manager": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "string-ts": "^2.3.1", "ts-pattern": "^5.9.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-NUGVZXgegv9l1zNNeX+n8EheGZtHcZGxBW6zmqUNr/762GikOgJHwaER8xDD073nKfEfFb+4JkBcpWRqAiTPnA=="], + + "eslint-plugin-react-x": ["eslint-plugin-react-x@2.4.0", "", { "dependencies": { "@eslint-react/ast": "2.4.0", "@eslint-react/core": "2.4.0", "@eslint-react/eff": "2.4.0", "@eslint-react/shared": "2.4.0", "@eslint-react/var": "2.4.0", "@typescript-eslint/scope-manager": "^8.50.1", "@typescript-eslint/type-utils": "^8.50.1", "@typescript-eslint/types": "^8.50.1", "@typescript-eslint/utils": "^8.50.1", "compare-versions": "^6.1.1", "is-immutable-type": "^5.0.1", "string-ts": "^2.3.1", "ts-api-utils": "^2.1.0", "ts-pattern": "^5.9.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ufKDXiDoMujcIT97Q6pCQs7j5q6Dtu/0AbXvrbNDLNXWVkCfZ7ayoRKMunvPU+WUHqnnzg9iv0o9QoaWwxG6rw=="], + + "eslint-plugin-regexp": ["eslint-plugin-regexp@2.10.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "comment-parser": "^1.4.0", "jsdoc-type-pratt-parser": "^4.0.0", "refa": "^0.12.1", "regexp-ast-analysis": "^0.7.1", "scslre": "^0.3.0" }, "peerDependencies": { "eslint": ">=8.44.0" } }, "sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng=="], + + "eslint-plugin-simple-import-sort": ["eslint-plugin-simple-import-sort@12.1.1", "", { "peerDependencies": { "eslint": ">=5.0.0" } }, "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA=="], + + "eslint-plugin-tailwindcss": ["eslint-plugin-tailwindcss@4.0.0-beta.0", "", { "dependencies": { "fast-glob": "^3.2.5", "postcss": "^8.4.4", "synckit": "^0.11.4", "tailwind-api-utils": "^1.0.3" }, "peerDependencies": { "tailwindcss": "^3.4.0 || ^4.0.0" } }, "sha512-WWCajZgQu38Sd67ZCl2W6i3MRzqB0d+H8s4qV9iB6lBJbsDOIpIlj6R1Fj2FXkoWErbo05pZnZYbCGIU9o/DsA=="], + + "eslint-plugin-unicorn": ["eslint-plugin-unicorn@62.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "@eslint-community/eslint-utils": "^4.9.0", "@eslint/plugin-kit": "^0.4.0", "change-case": "^5.4.4", "ci-info": "^4.3.1", "clean-regexp": "^1.0.0", "core-js-compat": "^3.46.0", "esquery": "^1.6.0", "find-up-simple": "^1.0.1", "globals": "^16.4.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "jsesc": "^3.1.0", "pluralize": "^8.0.0", "regexp-tree": "^0.1.27", "regjsparser": "^0.13.0", "semver": "^7.7.3", "strip-indent": "^4.1.1" }, "peerDependencies": { "eslint": ">=9.38.0" } }, "sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g=="], + + "eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.3.0", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + "fast-average-color": ["fast-average-color@9.5.0", "", {}, "sha512-nC6x2YIlJ9xxgkMFMd1BNoM1ctMjNoRKfRliPmiEWW3S6rLTHiQcy9g3pt/xiKv/D0NAAkhb9VyV+WJFvTqMGg=="], "fast-average-color-node": ["fast-average-color-node@3.1.0", "", { "dependencies": { "fast-average-color": "^9.4.0", "sharp": "^0.33.5" } }, "sha512-uJ7EWaNLwYPZXrW3DzSZLQGVTVTBZ/qPcpjFB5koyDSbUy3E7qhV9OdcPLn2mpBQH9p3D0G0vdUUbNwb5bTIfw=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "find-up-simple": ["find-up-simple@1.0.1", "", {}, "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + "giget": ["giget@1.2.5", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="], + "git-hooks-list": ["git-hooks-list@4.1.1", "", {}, "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], + + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + + "hosted-git-info": ["hosted-git-info@9.0.2", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], + "is-builtin-module": ["is-builtin-module@5.0.0", "", { "dependencies": { "builtin-modules": "^5.0.0" } }, "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA=="], + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-immutable-type": ["is-immutable-type@5.0.1", "", { "dependencies": { "@typescript-eslint/type-utils": "^8.0.0", "ts-api-utils": "^2.0.0", "ts-declaration-location": "^1.0.4" }, "peerDependencies": { "eslint": "*", "typescript": ">=4.7.4" } }, "sha512-LkHEOGVZZXxGl8vDs+10k3DvP++SEoYEAJLRk6buTFi6kD7QekThV7xHS0j6gpnUCQ0zpud/gMDGiV4dQneLTg=="], + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + "jsdoc-type-pratt-parser": ["jsdoc-type-pratt-parser@4.8.0", "", {}, "sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-eslint-parser": ["jsonc-eslint-parser@2.4.2", "", { "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^3.0.0", "espree": "^9.0.0", "semver": "^7.3.5" } }, "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lint-staged": ["lint-staged@16.2.7", "", { "dependencies": { "commander": "^14.0.2", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow=="], + + "listr2": ["listr2@9.0.5", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g=="], + + "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="], + "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "magic-bytes.js": ["magic-bytes.js@1.12.1", "", {}, "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], @@ -248,30 +697,106 @@ "mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nano-spawn": ["nano-spawn@2.0.0", "", {}, "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], "node-fetch-native": ["node-fetch-native@1.6.6", "", {}, "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ=="], + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + + "normalize-package-data": ["normalize-package-data@8.0.0", "", { "dependencies": { "hosted-git-info": "^9.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ=="], + "nypm": ["nypm@0.5.4", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "tinyexec": "^0.3.2", "ufo": "^1.5.4" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA=="], "ohash": ["ohash@1.1.6", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="], + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "open": ["open@10.1.2", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "package-json-validator": ["package-json-validator@0.59.0", "", { "dependencies": { "semver": "^7.7.2", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^7.0.0", "yargs": "~18.0.0" }, "bin": { "pjv": "lib/bin/pjv.mjs" } }, "sha512-WBTDKtO9pBa9GmA1sPbQHqlWxRdnHNfLFIIA49PPgV7px/rG27gHX57DWy77qyu374fla4veaIHy+gA+qRRuug=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], + "read-package-up": ["read-package-up@12.0.0", "", { "dependencies": { "find-up-simple": "^1.0.1", "read-pkg": "^10.0.0", "type-fest": "^5.2.0" } }, "sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw=="], + + "read-pkg": ["read-pkg@10.0.0", "", { "dependencies": { "@types/normalize-package-data": "^2.4.4", "normalize-package-data": "^8.0.0", "parse-json": "^8.3.0", "type-fest": "^5.2.0", "unicorn-magic": "^0.3.0" } }, "sha512-A70UlgfNdKI5NSvTTfHzLQj7NJRpJ4mT5tGafkllJ4wh71oYuGm/pzphHcmW4s35iox56KSK721AihodoXSc/A=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "refa": ["refa@0.12.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.8.0" } }, "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g=="], + + "regexp-ast-analysis": ["regexp-ast-analysis@0.7.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.8.0", "refa": "^0.12.1" } }, "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A=="], + + "regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="], + + "regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "requireindex": ["requireindex@1.2.0", "", {}, "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + "run-applescript": ["run-applescript@7.0.0", "", {}, "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "scslre": ["scslre@0.3.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.8.0", "refa": "^0.12.0", "regexp-ast-analysis": "^0.7.0" } }, "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ=="], + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], @@ -280,52 +805,226 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-git-hooks": ["simple-git-hooks@2.13.1", "", { "bin": { "simple-git-hooks": "cli.js" } }, "sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ=="], + "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], + "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "sort-object-keys": ["sort-object-keys@2.0.1", "", {}, "sha512-R89fO+z3x7hiKPXX5P0qim+ge6Y60AjtlW+QQpRozrrNcR1lw9Pkpm5MLB56HoNvdcLHL4wbpq16OcvGpEDJIg=="], + + "sort-package-json": ["sort-package-json@3.6.0", "", { "dependencies": { "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "git-hooks-list": "^4.1.1", "is-plain-obj": "^4.1.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.1", "tinyglobby": "^0.2.15" }, "bin": { "sort-package-json": "cli.js" } }, "sha512-fyJsPLhWvY7u2KsKPZn1PixbXp+1m7V8NWqU8CvgFRbMEX41Ffw1kD8n0CfJiGoaSfoAvbrqRRl/DcHO8omQOQ=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="], + + "stable-hash-x": ["stable-hash-x@0.2.0", "", {}, "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ=="], + + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + + "string-ts": ["string-ts@2.3.1", "", {}, "sha512-xSJq+BS52SaFFAVxuStmx6n5aYZU571uYUnUrPXkPFCfdHyZMMlbP2v2Wx5sNBnAVzq/2+0+mcBLBa3Xa5ubYw=="], + + "string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-indent": ["strip-indent@4.1.1", "", {}, "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], + + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + + "tailwind-api-utils": ["tailwind-api-utils@1.0.3", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "local-pkg": "^1.1.1" }, "peerDependencies": { "tailwindcss": "^3.3.0 || ^4.0.0 || ^4.0.0-beta" } }, "sha512-KpzUHkH1ug1sq4394SLJX38ZtpeTiqQ1RVyFTTSY2XuHsNSTWUkRo108KmyyrMWdDbQrLYkSHaNKj/a3bmA4sQ=="], + + "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "ts-declaration-location": ["ts-declaration-location@1.0.7", "", { "dependencies": { "picomatch": "^4.0.2" }, "peerDependencies": { "typescript": ">=4.0.0" } }, "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA=="], + "ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="], + "ts-pattern": ["ts-pattern@5.9.0", "", {}, "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-fest": ["type-fest@5.3.1", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg=="], + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "typescript-eslint": ["typescript-eslint@8.50.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.50.1", "@typescript-eslint/parser": "8.50.1", "@typescript-eslint/typescript-estree": "8.50.1", "@typescript-eslint/utils": "8.50.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ=="], + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], + "unconfig": ["unconfig@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.4.2" } }, "sha512-nrMlWRQ1xdTjSnSUqvYqJzbTBFugoqHobQj58B2bc8qxHKBBHMNNsWQFP3Cd3/JZK907voM2geYPWqD4VK3MPQ=="], + + "unconfig-core": ["unconfig-core@7.4.2", "", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-VgPCvLWugINbXvMQDf8Jh0mlbvNjNC6eSUziHsBCMpxR05OPrNrvDnyatdMjRgcHaaNsCqz+wjNXxNw1kRLHUg=="], + "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="], "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + + "validate-npm-package-name": ["validate-npm-package-name@7.0.1", "", {}, "sha512-BM0Upcemlce8/9+HE+/VpWqn3u3mYh6Om/FEC8yPMnEHwf710fW5Q6fhjT1SQyRlZD1G9CJbgfH+rWgAcIvjlQ=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + + "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], + + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@4.2.1", "", {}, "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw=="], + + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], + "@es-joy/jsdoccomment/jsdoc-type-pratt-parser": ["jsdoc-type-pratt-parser@7.0.0", "", {}, "sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@eslint/eslintrc/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@quansync/fs/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + "@sapphire/pieces/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "c12/jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + + "clean-regexp/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "eslint-compat-utils/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "eslint-plugin-import-x/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], + + "eslint-plugin-package-json/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "eslint-plugin-unicorn/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "hosted-git-info/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], + + "jsonc-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "jsonc-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "jsonc-eslint-parser/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "lint-staged/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "local-pkg/pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "normalize-package-data/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "nypm/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "package-json-validator/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "parse-json/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "sort-package-json/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "unconfig/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + + "unconfig-core/quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "yargs/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "local-pkg/pkg-types/confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + + "local-pkg/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], } } diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..71a0973 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "@richardscull/eslint-config"; + +export default defineConfig({ + fileCase: "kebabCase", + ignores: ["**/types/api/**"], // Generated files by hey-api +}); diff --git a/global.d.ts b/global.d.ts index 5456c68..bdf52f6 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,29 +1,30 @@ -import Database from "bun:sqlite" -import type { IConfig } from "./src/lib/configs/env" -import type { EmbedPresetsUtility } from "./src/utilities/embed-presets.utility" -import type { PaginationUtility } from "./src/utilities/pagination.utility" -import type { ActionStoreUtility } from "./src/utilities/action-store.utility" +import type Database from "bun:sqlite"; + +import type { IConfig } from "./src/lib/configs/env"; +import type { ActionStoreUtility } from "./src/utilities/action-store.utility"; +import type { EmbedPresetsUtility } from "./src/utilities/embed-presets.utility"; +import type { PaginationUtility } from "./src/utilities/pagination.utility"; declare module "@sapphire/pieces" { interface Container { - config: IConfig + config: IConfig; sunrise: { - websocket: WebSocket - } - db: Database + websocket: WebSocket; + }; + db: Database; } } declare module "@sapphire/plugin-utilities-store" { interface Utilities { - embedPresets: EmbedPresetsUtility - actionStore: ActionStoreUtility - pagination: PaginationUtility + embedPresets: EmbedPresetsUtility; + actionStore: ActionStoreUtility; + pagination: PaginationUtility; } } declare module "discord.js" { interface Client { - guild?: Guild + guild?: Guild; } } diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts index f266522..337b748 100644 --- a/openapi-ts.config.ts +++ b/openapi-ts.config.ts @@ -1,4 +1,4 @@ -import { config } from "./src/lib/configs/env" +import { config } from "./src/lib/configs/env"; export default { input: `https://api.${config.sunrise.uri}/openapi/v1.json`, @@ -14,4 +14,4 @@ export default { name: "@hey-api/typescript", }, ], -} +}; diff --git a/package.json b/package.json index 6e04db1..2766e03 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,11 @@ "private": true, "main": "src/index.ts", "scripts": { - "start": "bun src/index.ts", "dev": "bun --watch src/index.ts", - "generate:openapi": "dotenv -e .env -- openapi-ts" - }, - "devDependencies": { - "@hey-api/openapi-ts": "^0.78.1", - "@types/bun": "latest", - "dotenv-cli": "^8.0.0" + "generate:openapi": "dotenv -e .env -- openapi-ts", + "lint": "eslint --fix", + "lint:check": "eslint", + "start": "bun src/index.ts" }, "peerDependencies": { "typescript": "^5" @@ -29,5 +26,16 @@ "bun-sqlite-migrations": "^1.0.2", "discord.js": "^14.21.0", "fast-average-color-node": "^3.1.0" + }, + "devDependencies": { + "@hey-api/openapi-ts": "^0.78.1", + "@richardscull/eslint-config": "^1.0.6", + "@types/bun": "latest", + "dotenv-cli": "^8.0.0", + "eslint": "^9.39.2", + "jiti": "^2.6.1", + "lint-staged": "^16.2.7", + "simple-git-hooks": "^2.13.1", + "typescript": "^5" } } diff --git a/src/client.ts b/src/client.ts index 4530ac8..48a986c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,20 +1,21 @@ import { ApplicationCommandRegistries, + container, LogLevel, RegisterBehavior, SapphireClient, - container, -} from "@sapphire/framework" -import { Time } from "@sapphire/time-utilities" -import { GatewayIntentBits } from "discord.js" -import { config } from "./lib/configs/env" -import { client as apiClient } from "./lib/types/api/client.gen" -import type { WebSocketEventType } from "./lib/types/api" -import { db } from "./database" +} from "@sapphire/framework"; +import { Time } from "@sapphire/time-utilities"; +import { GatewayIntentBits } from "discord.js"; + +import { db } from "./database"; +import { config } from "./lib/configs/env"; +import type { WebSocketEventType } from "./lib/types/api"; +import { client as apiClient } from "./lib/types/api/client.gen"; export class SunshineClient extends SapphireClient { - private websocketHeartbeatTimeout: NodeJS.Timeout | null = null - private readonly serverApiURI = "api." + config.sunrise.uri + private websocketHeartbeatTimeout: NodeJS.Timeout | null = null; + private readonly serverApiURI = `api.${config.sunrise.uri}`; public constructor() { super({ @@ -26,64 +27,65 @@ export class SunshineClient extends SapphireClient { enableLoaderTraceLoggings: true, loadDefaultErrorListeners: false, loadMessageCommandListeners: true, - }) + }); - this.init() - this.initWebsocket() + this.init(); + this.initWebsocket(); } private init() { - ApplicationCommandRegistries.setDefaultBehaviorWhenNotIdentical(RegisterBehavior.BulkOverwrite) + ApplicationCommandRegistries.setDefaultBehaviorWhenNotIdentical(RegisterBehavior.BulkOverwrite); - apiClient.setConfig({ baseUrl: "https://" + this.serverApiURI }) + apiClient.setConfig({ baseUrl: `https://${this.serverApiURI}` }); - container.config = config - container.db = db + container.config = config; + container.db = db; } private initWebsocket() { container.sunrise = { websocket: new WebSocket(`wss://${this.serverApiURI}/ws`), - } + }; container.sunrise.websocket.addEventListener("open", () => { - container.logger.info("Server's Websocket: WebSocket connected!") + container.logger.info("Server's Websocket: WebSocket connected!"); this.websocketHeartbeatTimeout = setInterval(() => { if (container.sunrise.websocket.readyState === WebSocket.OPEN) { - container.sunrise.websocket.send(JSON.stringify({ type: "ping" })) - container.logger.debug(`Sent heartbeat for server's websocket`) + container.sunrise.websocket.send(JSON.stringify({ type: "ping" })); + container.logger.debug(`Sent heartbeat for server's websocket`); } - }, 30 * Time.Second) - }) + }, 30 * Time.Second); + }); container.sunrise.websocket.addEventListener("error", (e) => { - container.logger.error(`Server's Websocket:`, e) - }) + container.logger.error(`Server's Websocket:`, e); + }); container.sunrise.websocket.addEventListener("close", (e) => { - if (this.websocketHeartbeatTimeout) clearTimeout(this.websocketHeartbeatTimeout) + if (this.websocketHeartbeatTimeout) + clearTimeout(this.websocketHeartbeatTimeout); container.logger.error( `Server's Websocket: Connection Closed: "${e.reason}". Trying to reconnect`, - ) + ); - setTimeout(this.initWebsocket.bind(this), 5 * Time.Second) - }) + setTimeout(this.initWebsocket.bind(this), 5 * Time.Second); + }); container.sunrise.websocket.addEventListener("message", (e) => { - const { data: dataRaw } = e + const { data: dataRaw } = e; const data = JSON.parse(dataRaw) as { - type: WebSocketEventType - data: any - } + type: WebSocketEventType; + data: any; + }; if (!data.type || !data.data) { - return + return; } - container.client.ws.emit(data.type, data.data) - }) + container.client.ws.emit(data.type, data.data); + }); } } diff --git a/src/commands/meow.command.ts b/src/commands/meow.command.ts index c072dc0..86c6a53 100644 --- a/src/commands/meow.command.ts +++ b/src/commands/meow.command.ts @@ -1,11 +1,11 @@ -import { Command } from "@sapphire/framework" +import { Command } from "@sapphire/framework"; export class MeowCommand extends Command { public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => builder.setName("meow").setDescription("meow?")) + registry.registerChatInputCommand(builder => builder.setName("meow").setDescription("meow?")); } public override chatInputRun(interaction: Command.ChatInputCommandInteraction) { - return interaction.reply("meow! 😺") + return interaction.reply("meow! 😺"); } } diff --git a/src/commands/osu.command.ts b/src/commands/osu.command.ts index b3a2279..70181c7 100644 --- a/src/commands/osu.command.ts +++ b/src/commands/osu.command.ts @@ -1,14 +1,14 @@ -import { Command } from "@sapphire/framework" -import { Subcommand } from "@sapphire/plugin-subcommands" -import { ApplyOptions } from "@sapphire/decorators" -import { SlashCommandSubcommandBuilder } from "discord.js" - -import * as profile from "../subcommands/osu/profile.subcommand" -import * as score from "../subcommands/osu/score.subcommand" -import * as link from "../subcommands/osu/link.subcommand" -import * as unlink from "../subcommands/osu/unlink.subcommand" -import * as recentScore from "../subcommands/osu/recent-score.subcommand" -import * as scores from "../subcommands/osu/scores.subcommand" +import { ApplyOptions } from "@sapphire/decorators"; +import type { Command } from "@sapphire/framework"; +import { Subcommand } from "@sapphire/plugin-subcommands"; +import { SlashCommandSubcommandBuilder } from "discord.js"; + +import * as link from "../subcommands/osu/link.subcommand"; +import * as profile from "../subcommands/osu/profile.subcommand"; +import * as recentScore from "../subcommands/osu/recent-score.subcommand"; +import * as score from "../subcommands/osu/score.subcommand"; +import * as scores from "../subcommands/osu/scores.subcommand"; +import * as unlink from "../subcommands/osu/unlink.subcommand"; const rawModules = [ { add: profile.addProfileSubcommand, run: profile.chatInputRunProfileSubcommand }, @@ -17,20 +17,20 @@ const rawModules = [ { add: unlink.addUnlinkSubcommand, run: unlink.chatInputRunUnlinkSubcommand }, { add: recentScore.addRecentScoreSubcommand, run: recentScore.chatInputRunRecentScoreSubcommand }, { add: scores.addScoresSubcommand, run: scores.chatInputRunScoresSubcommand }, -] +]; const subcommandModules = rawModules.map((cmd) => { - const builder = new SlashCommandSubcommandBuilder() - const subcommandName = cmd.add(builder).name + const builder = new SlashCommandSubcommandBuilder(); + const subcommandName = cmd.add(builder).name; - const camelName = subcommandName.replace(/-(\w)/g, (_, letter) => letter.toUpperCase()) - return { ...cmd, name: subcommandName, camelName } -}) + const camelName = subcommandName.replaceAll(/-(\w)/g, (_, letter) => letter.toUpperCase()); + return { ...cmd, name: subcommandName, camelName }; +}); -const subcommandOptions = subcommandModules.map((cmd) => ({ +const subcommandOptions = subcommandModules.map(cmd => ({ name: cmd.name, chatInputRun: cmd.camelName, -})) +})); @ApplyOptions({ subcommands: subcommandOptions, @@ -38,23 +38,23 @@ const subcommandOptions = subcommandModules.map((cmd) => ({ export class OsuCommand extends Subcommand { public override registerApplicationCommands(registry: Command.Registry) { registry.registerChatInputCommand((builder) => { - const osu = builder.setName("osu").setDescription("Server's commands") + const osu = builder.setName("osu").setDescription("Server's commands"); for (const cmd of subcommandModules) { - osu.addSubcommand(cmd.add) + osu.addSubcommand(cmd.add); } - return osu - }) + return osu; + }); } constructor(context: Subcommand.LoaderContext, options: Subcommand.Options) { - super(context, options) + super(context, options); for (const cmd of subcommandModules) { ;(this as any)[cmd.name] = async (interaction: Subcommand.ChatInputCommandInteraction) => { - return (cmd.run as any).call(this, interaction) - } + return (cmd.run as any).call(this, interaction); + }; } } } diff --git a/src/commands/tests/meow.command.test.ts b/src/commands/tests/meow.command.test.ts index 75f4476..1866987 100644 --- a/src/commands/tests/meow.command.test.ts +++ b/src/commands/tests/meow.command.test.ts @@ -1,34 +1,36 @@ -import { expect, describe, it, mock, jest, beforeAll, afterAll, beforeEach } from "bun:test" -import { MeowCommand } from "../meow.command" -import { Mocker } from "../../lib/mock/mocker" -import { FakerGenerator } from "../../lib/mock/faker.generator" +import type { jest } from "bun:test"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, mock } from "bun:test"; + +import { FakerGenerator } from "../../lib/mock/faker.generator"; +import { Mocker } from "../../lib/mock/mocker"; +import { MeowCommand } from "../meow.command"; describe("Meow Command", () => { - let meowCommand: MeowCommand - let errorHandler: jest.Mock + let meowCommand: MeowCommand; + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - meowCommand = Mocker.createCommandInstance(MeowCommand) - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + meowCommand = Mocker.createCommandInstance(MeowCommand); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); it("should reply with 'meow! 😺' when chatInputRun is called", async () => { - const replyMock = mock() + const replyMock = mock(); const interaction = FakerGenerator.generateInteraction({ reply: replyMock, - }) + }); - await meowCommand.chatInputRun(interaction) + await meowCommand.chatInputRun(interaction); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); - expect(replyMock).toHaveBeenCalledWith("meow! 😺") - }) -}) + expect(replyMock).toHaveBeenCalledWith("meow! 😺"); + }); +}); diff --git a/src/database/index.ts b/src/database/index.ts index e418bbe..b985076 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -1,11 +1,12 @@ -import path from "path" -import Database from "bun:sqlite" -import { getMigrations, migrate } from "bun-sqlite-migrations" +import path from "node:path"; -const databaseFactory = () => { - const db = new Database(path.resolve(process.cwd(), "data", "sunshine.db"), { create: true }) - migrate(db, getMigrations(path.resolve(process.cwd(), "data", "migrations"))) - return db +import Database from "bun:sqlite"; +import { getMigrations, migrate } from "bun-sqlite-migrations"; + +function databaseFactory() { + const db = new Database(path.resolve(process.cwd(), "data", "sunshine.db"), { create: true }); + migrate(db, getMigrations(path.resolve(process.cwd(), "data", "migrations"))); + return db; } -export const db = databaseFactory() +export const db = databaseFactory(); diff --git a/src/index.ts b/src/index.ts index 6bfada1..bdfc88d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -import { config } from "./lib/configs/env" -import { SunshineClient } from "./client" +import "@sapphire/plugin-logger/register"; +import "@sapphire/plugin-utilities-store/register"; -import "@sapphire/plugin-logger/register" -import "@sapphire/plugin-utilities-store/register" +import { SunshineClient } from "./client"; +import { config } from "./lib/configs/env"; -const client = new SunshineClient() -client.login(config.discord.token) +const client = new SunshineClient(); +client.login(config.discord.token); diff --git a/src/interaction-handlers/pagination/buttons/pagination.button.ts b/src/interaction-handlers/pagination/buttons/pagination.button.ts index 00a834f..39cc923 100644 --- a/src/interaction-handlers/pagination/buttons/pagination.button.ts +++ b/src/interaction-handlers/pagination/buttons/pagination.button.ts @@ -1,20 +1,20 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { InteractionHandler, InteractionHandlerTypes } from "@sapphire/framework" +import { ApplyOptions } from "@sapphire/decorators"; +import { InteractionHandler, InteractionHandlerTypes } from "@sapphire/framework"; +import type { ButtonInteraction } from "discord.js"; import { ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, - type ButtonInteraction, -} from "discord.js" -import { ensureUsedBySameUser, validCustomId } from "../../../lib/utils/decorator.util" -import { buildCustomId, parseCustomId } from "../../../lib/utils/discord.util" +} from "discord.js"; -import { PaginationButtonAction } from "../../../lib/types/enum/pagination.types" -import type { PaginationStore } from "../../../lib/types/store.types" -import { PaginationInteractionCustomId } from "../../../lib/types/enum/custom-ids.types" -import { EMPTY_CHAR } from "../../../lib/constants" -import { PaginationTextInputCustomIds } from "../../../lib/types/enum/text-input.types" +import { EMPTY_CHAR } from "../../../lib/constants"; +import { PaginationInteractionCustomId } from "../../../lib/types/enum/custom-ids.types"; +import { PaginationButtonAction } from "../../../lib/types/enum/pagination.types"; +import { PaginationTextInputCustomIds } from "../../../lib/types/enum/text-input.types"; +import type { PaginationStore } from "../../../lib/types/store.types"; +import { ensureUsedBySameUser, validCustomId } from "../../../lib/utils/decorator.util"; +import { buildCustomId, parseCustomId } from "../../../lib/utils/discord.util"; @ApplyOptions({ interactionHandlerType: InteractionHandlerTypes.Button, @@ -23,21 +23,21 @@ export class PaginationButton extends InteractionHandler { @ensureUsedBySameUser() @validCustomId(PaginationInteractionCustomId.PAGINATION_ACTION_MOVE) public override async parse(interaction: ButtonInteraction) { - const { ctx } = parseCustomId(interaction.customId) + const { ctx } = parseCustomId(interaction.customId); if (!ctx.dataStoreId || !ctx.data) { - return this.none() + return this.none(); } - const data = this.container.utilities.actionStore.get(ctx.dataStoreId) + const data = this.container.utilities.actionStore.get(ctx.dataStoreId); if (!data) { - return this.none() + return this.none(); } return this.some({ dataStoreId: ctx.dataStoreId, ...data, paginationAction: ctx.data as PaginationButtonAction, - }) + }); } public override async run( @@ -46,13 +46,13 @@ export class PaginationButton extends InteractionHandler { ) { switch (paginationAction) { case PaginationButtonAction.MAX_LEFT: - state.currentPage = 1 - break + state.currentPage = 1; + break; case PaginationButtonAction.LEFT: - state.currentPage-- - break + state.currentPage--; + break; case PaginationButtonAction.SELECT_PAGE: - const modal = new ModalBuilder() + return interaction.showModal(new ModalBuilder() .setTitle(EMPTY_CHAR) .setCustomId( buildCustomId( @@ -69,33 +69,31 @@ export class PaginationButton extends InteractionHandler { .setStyle(TextInputStyle.Short) .setCustomId(PaginationTextInputCustomIds.GO_TO_PAGE_NUMBER), ), - ) - - return interaction.showModal(modal) + )); case PaginationButtonAction.RIGHT: - state.currentPage++ - break + state.currentPage++; + break; case PaginationButtonAction.MAX_RIGHT: - state.currentPage = state.totalPages - break + state.currentPage = state.totalPages; + break; default: - throw new Error("Unexpected customId for pagination") + throw new Error("Unexpected customId for pagination"); } - await interaction.deferUpdate() + await interaction.deferUpdate(); - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; await interaction.editReply({ embeds: [embedPresets.getSuccessEmbed("⌛ Please wait...")], components: [], - }) + }); - const { buttonsRow, embed } = await handleSetPage(state) + const { buttonsRow, embed } = await handleSetPage(state); await interaction.editReply({ embeds: [embed], components: [buttonsRow], - }) + }); } } diff --git a/src/interaction-handlers/pagination/buttons/tests/pagination.button.test.ts b/src/interaction-handlers/pagination/buttons/tests/pagination.button.test.ts index 7b253e9..0b7ef1e 100644 --- a/src/interaction-handlers/pagination/buttons/tests/pagination.button.test.ts +++ b/src/interaction-handlers/pagination/buttons/tests/pagination.button.test.ts @@ -1,103 +1,105 @@ -import { faker } from "@faker-js/faker" -import { container, InteractionHandlerStore, InteractionHandlerTypes } from "@sapphire/framework" -import { expect, describe, it, beforeAll, afterAll, jest, mock, beforeEach } from "bun:test" -import { FakerGenerator } from "../../../../lib/mock/faker.generator" -import { Mocker } from "../../../../lib/mock/mocker" -import { PaginationButton } from "../pagination.button" -import { PaginationInteractionCustomId } from "../../../../lib/types/enum/custom-ids.types" -import { PaginationButtonAction } from "../../../../lib/types/enum/pagination.types" -import { EMPTY_CHAR } from "../../../../lib/constants" +import { faker } from "@faker-js/faker"; +import { container, InteractionHandlerStore, InteractionHandlerTypes } from "@sapphire/framework"; +import type { jest } from "bun:test"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, mock } from "bun:test"; + +import { EMPTY_CHAR } from "../../../../lib/constants"; +import { FakerGenerator } from "../../../../lib/mock/faker.generator"; +import { Mocker } from "../../../../lib/mock/mocker"; +import { PaginationInteractionCustomId } from "../../../../lib/types/enum/custom-ids.types"; +import { PaginationButtonAction } from "../../../../lib/types/enum/pagination.types"; +import { PaginationButton } from "../pagination.button"; describe("Pagination Button", () => { - let errorHandler: jest.Mock + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); describe("parse", () => { it("should return none when invalid custom id is provided", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); const interaction = FakerGenerator.generateButtonInteraction({ customId: "invalid_custom_id", - }) + }); - const result = await button.parse(interaction) + const result = await button.parse(interaction); - expect(result).toBe(button.none()) - }) + expect(result).toBe(button.none()); + }); it("should return none when no dataStoreId is provided", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const userId = faker.number.int().toString() + const userId = faker.number.int().toString(); const customId = FakerGenerator.generateCustomId({ prefix: PaginationInteractionCustomId.PAGINATION_ACTION_MOVE, userId, ctx: {}, - }) + }); const interaction = FakerGenerator.generateButtonInteraction({ user: { id: userId, }, - customId: customId, - }) + customId, + }); - const result = await button.parse(interaction) + const result = await button.parse(interaction); - expect(result).toBe(button.none()) - }) + expect(result).toBe(button.none()); + }); it("should return none when no data is provided", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const dataStoreId = container.utilities.actionStore.set(mock()) - const userId = faker.number.int().toString() + const dataStoreId = container.utilities.actionStore.set(mock()); + const userId = faker.number.int().toString(); const customId = FakerGenerator.generateCustomId({ prefix: PaginationInteractionCustomId.PAGINATION_ACTION_MOVE, userId, ctx: { dataStoreId }, - }) + }); const interaction = FakerGenerator.generateButtonInteraction({ user: { id: userId, }, - customId: customId, - }) + customId, + }); - const result = await button.parse(interaction) + const result = await button.parse(interaction); - expect(result).toBe(button.none()) - }) + expect(result).toBe(button.none()); + }); it("should return none when dataStore does not exist", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const userId = faker.number.int().toString() + const userId = faker.number.int().toString(); const customId = FakerGenerator.generateCustomId({ prefix: PaginationInteractionCustomId.PAGINATION_ACTION_MOVE, @@ -106,30 +108,30 @@ describe("Pagination Button", () => { dataStoreId: faker.string.uuid(), data: [PaginationButtonAction.LEFT], }, - }) + }); const interaction = FakerGenerator.generateButtonInteraction({ user: { id: userId, }, - customId: customId, - }) + customId, + }); - const result = await button.parse(interaction) + const result = await button.parse(interaction); - expect(result).toBe(button.none()) - }) + expect(result).toBe(button.none()); + }); it("should return some when valid button interaction is provided", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const paginationData = FakerGenerator.generatePaginationData() - const dataStoreId = container.utilities.actionStore.set(paginationData) + const paginationData = FakerGenerator.generatePaginationData(); + const dataStoreId = container.utilities.actionStore.set(paginationData); - const userId = faker.number.int().toString() + const userId = faker.number.int().toString(); const customId = FakerGenerator.generateCustomId({ prefix: PaginationInteractionCustomId.PAGINATION_ACTION_MOVE, @@ -138,32 +140,32 @@ describe("Pagination Button", () => { dataStoreId, data: [PaginationButtonAction.RIGHT], }, - }) + }); const interaction = FakerGenerator.generateButtonInteraction({ user: { id: userId, }, - customId: customId, - }) + customId, + }); - const result = await button.parse(interaction) + const result = await button.parse(interaction); - expect(result).not.toBe(button.none()) - }) - }) + expect(result).not.toBe(button.none()); + }); + }); describe("run", () => { it("should navigate to first page when MAX_LEFT is clicked", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const mockHandleSetPage = mock(async (state: any) => ({ + const mockHandleSetPage = mock(async (_state: any) => ({ embed: { title: "Page 1" }, buttonsRow: { components: [] }, - })) + })); const paginationData = FakerGenerator.generatePaginationData({ handleSetPage: mockHandleSetPage, @@ -171,37 +173,37 @@ describe("Pagination Button", () => { currentPage: 3, totalPages: 5, }, - }) + }); - const deferUpdateMock = mock(async () => ({})) - const editReplyMock = mock(async () => ({})) + const deferUpdateMock = mock(async () => ({})); + const editReplyMock = mock(async () => ({})); const interaction = FakerGenerator.generateButtonInteraction({ deferUpdate: deferUpdateMock, editReply: editReplyMock, - }) + }); await button.run(interaction, { ...paginationData, paginationAction: PaginationButtonAction.MAX_LEFT, - } as any) + } as any); - expect(deferUpdateMock).toHaveBeenCalled() - expect(paginationData.state.currentPage).toBe(1) - expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state) - expect(editReplyMock).toHaveBeenCalledTimes(2) - }) + expect(deferUpdateMock).toHaveBeenCalled(); + expect(paginationData.state.currentPage).toBe(1); + expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state); + expect(editReplyMock).toHaveBeenCalledTimes(2); + }); it("should navigate to previous page when LEFT is clicked", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const mockHandleSetPage = mock(async (state: any) => ({ + const mockHandleSetPage = mock(async (_state: any) => ({ embed: { title: "Page 2" }, buttonsRow: { components: [] }, - })) + })); const paginationData = FakerGenerator.generatePaginationData({ handleSetPage: mockHandleSetPage, @@ -209,53 +211,53 @@ describe("Pagination Button", () => { currentPage: 3, totalPages: 5, }, - }) + }); - const deferUpdateMock = mock(async () => ({})) - const editReplyMock = mock(async () => ({})) + const deferUpdateMock = mock(async () => ({})); + const editReplyMock = mock(async () => ({})); const interaction = FakerGenerator.generateButtonInteraction({ deferUpdate: deferUpdateMock, editReply: editReplyMock, - }) + }); await button.run(interaction, { ...paginationData, paginationAction: PaginationButtonAction.LEFT, - } as any) + } as any); - expect(deferUpdateMock).toHaveBeenCalled() - expect(paginationData.state.currentPage).toBe(2) - expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state) - expect(editReplyMock).toHaveBeenCalledTimes(2) - }) + expect(deferUpdateMock).toHaveBeenCalled(); + expect(paginationData.state.currentPage).toBe(2); + expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state); + expect(editReplyMock).toHaveBeenCalledTimes(2); + }); it("should show modal when SELECT_PAGE is clicked", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const paginationData = FakerGenerator.generatePaginationData() - const dataStoreId = container.utilities.actionStore.set(paginationData) + const paginationData = FakerGenerator.generatePaginationData(); + const dataStoreId = container.utilities.actionStore.set(paginationData); - const showModalMock = mock() - const userId = faker.number.int().toString() + const showModalMock = mock(); + const userId = faker.number.int().toString(); const interaction = FakerGenerator.generateButtonInteraction({ showModal: showModalMock, user: { id: userId, }, - }) + }); await button.run(interaction, { ...paginationData, dataStoreId, paginationAction: PaginationButtonAction.SELECT_PAGE, - } as any) + } as any); - expect(showModalMock).toHaveBeenCalled() + expect(showModalMock).toHaveBeenCalled(); expect(showModalMock).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ @@ -265,19 +267,19 @@ describe("Pagination Button", () => { ), }), }), - ) - }) + ); + }); it("should navigate to next page when RIGHT is clicked", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const mockHandleSetPage = mock(async (state: any) => ({ + const mockHandleSetPage = mock(async (_state: any) => ({ embed: { title: "Page 4" }, buttonsRow: { components: [] }, - })) + })); const paginationData = FakerGenerator.generatePaginationData({ handleSetPage: mockHandleSetPage, @@ -285,37 +287,37 @@ describe("Pagination Button", () => { currentPage: 3, totalPages: 5, }, - }) + }); - const deferUpdateMock = mock(async () => ({})) - const editReplyMock = mock(async () => ({})) + const deferUpdateMock = mock(async () => ({})); + const editReplyMock = mock(async () => ({})); const interaction = FakerGenerator.generateButtonInteraction({ deferUpdate: deferUpdateMock, editReply: editReplyMock, - }) + }); await button.run(interaction, { ...paginationData, paginationAction: PaginationButtonAction.RIGHT, - } as any) + } as any); - expect(deferUpdateMock).toHaveBeenCalled() - expect(paginationData.state.currentPage).toBe(4) - expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state) - expect(editReplyMock).toHaveBeenCalledTimes(2) - }) + expect(deferUpdateMock).toHaveBeenCalled(); + expect(paginationData.state.currentPage).toBe(4); + expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state); + expect(editReplyMock).toHaveBeenCalledTimes(2); + }); it("should navigate to last page when MAX_RIGHT is clicked", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const mockHandleSetPage = mock(async (state: any) => ({ + const mockHandleSetPage = mock(async (_state: any) => ({ embed: { title: "Page 5" }, buttonsRow: { components: [] }, - })) + })); const paginationData = FakerGenerator.generatePaginationData({ handleSetPage: mockHandleSetPage, @@ -323,37 +325,37 @@ describe("Pagination Button", () => { currentPage: 3, totalPages: 5, }, - }) + }); - const deferUpdateMock = mock(async () => ({})) - const editReplyMock = mock(async () => ({})) + const deferUpdateMock = mock(async () => ({})); + const editReplyMock = mock(async () => ({})); const interaction = FakerGenerator.generateButtonInteraction({ deferUpdate: deferUpdateMock, editReply: editReplyMock, - }) + }); await button.run(interaction, { ...paginationData, paginationAction: PaginationButtonAction.MAX_RIGHT, - } as any) + } as any); - expect(deferUpdateMock).toHaveBeenCalled() - expect(paginationData.state.currentPage).toBe(5) - expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state) - expect(editReplyMock).toHaveBeenCalledTimes(2) - }) + expect(deferUpdateMock).toHaveBeenCalled(); + expect(paginationData.state.currentPage).toBe(5); + expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state); + expect(editReplyMock).toHaveBeenCalledTimes(2); + }); it("should show loading message before calling handleSetPage", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const mockHandleSetPage = mock(async (state: any) => ({ + const mockHandleSetPage = mock(async (_state: any) => ({ embed: { title: "Page 2" }, buttonsRow: { components: [] }, - })) + })); const paginationData = FakerGenerator.generatePaginationData({ handleSetPage: mockHandleSetPage, @@ -361,20 +363,20 @@ describe("Pagination Button", () => { currentPage: 1, totalPages: 5, }, - }) + }); - const deferUpdateMock = mock(async () => ({})) - const editReplyMock = mock(async () => ({})) + const deferUpdateMock = mock(async () => ({})); + const editReplyMock = mock(async () => ({})); const interaction = FakerGenerator.generateButtonInteraction({ deferUpdate: deferUpdateMock, editReply: editReplyMock, - }) + }); await button.run(interaction, { ...paginationData, paginationAction: PaginationButtonAction.RIGHT, - } as any) + } as any); expect(editReplyMock).toHaveBeenNthCalledWith(1, { embeds: [ @@ -385,34 +387,34 @@ describe("Pagination Button", () => { }), ], components: [], - }) + }); expect(editReplyMock).toHaveBeenNthCalledWith(2, { embeds: [expect.objectContaining({ title: "Page 2" })], components: [{ components: [] }], - }) - }) + }); + }); it("should throw error when unexpected pagination action is provided", async () => { const button = new PaginationButton( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.Button }, - ) + ); - const paginationData = FakerGenerator.generatePaginationData() + const paginationData = FakerGenerator.generatePaginationData(); - const deferUpdateMock = mock(async () => ({})) + const deferUpdateMock = mock(async () => ({})); const interaction = FakerGenerator.generateButtonInteraction({ deferUpdate: deferUpdateMock, - }) + }); expect( button.run(interaction, { ...paginationData, paginationAction: "INVALID_ACTION" as any, } as any), - ).rejects.toThrow("Unexpected customId for pagination") - }) - }) -}) + ).rejects.toThrow("Unexpected customId for pagination"); + }); + }); +}); diff --git a/src/interaction-handlers/pagination/models/pagination-set-page.model.ts b/src/interaction-handlers/pagination/models/pagination-set-page.model.ts index e49daad..1d77930 100644 --- a/src/interaction-handlers/pagination/models/pagination-set-page.model.ts +++ b/src/interaction-handlers/pagination/models/pagination-set-page.model.ts @@ -1,12 +1,13 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { InteractionHandlerTypes, InteractionHandler } from "@sapphire/framework" -import type { ButtonInteraction, ModalSubmitInteraction } from "discord.js" -import { ensureUsedBySameUser, validCustomId } from "../../../lib/utils/decorator.util" -import { PaginationInteractionCustomId } from "../../../lib/types/enum/custom-ids.types" -import type { PaginationStore } from "../../../lib/types/store.types" -import { parseCustomId } from "../../../lib/utils/discord.util" -import { PaginationTextInputCustomIds } from "../../../lib/types/enum/text-input.types" -import { ExtendedError } from "../../../lib/extended-error" +import { ApplyOptions } from "@sapphire/decorators"; +import { InteractionHandler, InteractionHandlerTypes } from "@sapphire/framework"; +import type { ModalSubmitInteraction } from "discord.js"; + +import { ExtendedError } from "../../../lib/extended-error"; +import { PaginationInteractionCustomId } from "../../../lib/types/enum/custom-ids.types"; +import { PaginationTextInputCustomIds } from "../../../lib/types/enum/text-input.types"; +import type { PaginationStore } from "../../../lib/types/store.types"; +import { ensureUsedBySameUser, validCustomId } from "../../../lib/utils/decorator.util"; +import { parseCustomId } from "../../../lib/utils/discord.util"; @ApplyOptions({ interactionHandlerType: InteractionHandlerTypes.ModalSubmit, @@ -15,53 +16,53 @@ export class PaginationSetPageModal extends InteractionHandler { @ensureUsedBySameUser() @validCustomId(PaginationInteractionCustomId.PAGINATION_ACTION_SELECT_PAGE) public override async parse(interaction: ModalSubmitInteraction) { - const { ctx } = parseCustomId(interaction.customId) + const { ctx } = parseCustomId(interaction.customId); if (!ctx.dataStoreId) { - return this.none() + return this.none(); } - const data = this.container.utilities.actionStore.get(ctx.dataStoreId) + const data = this.container.utilities.actionStore.get(ctx.dataStoreId); if (!data) { - return this.none() + return this.none(); } - return this.some(data) + return this.some(data); } public override async run( interaction: ModalSubmitInteraction, { handleSetPage, state }: InteractionHandler.ParseResult, ) { - await interaction.deferUpdate() + await interaction.deferUpdate(); const page = interaction.fields.getTextInputValue( PaginationTextInputCustomIds.GO_TO_PAGE_NUMBER, - ) + ); - if (isNaN(Number(page))) { - throw new ExtendedError("Not a number") + if (Number.isNaN(Number(page))) { + throw new ExtendedError("Not a number"); } - const pageNum = Number(page) + const pageNum = Number(page); if (pageNum <= 0 || pageNum > state.totalPages) { - throw new ExtendedError("Invalid page") + throw new ExtendedError("Invalid page"); } - state.currentPage = pageNum + state.currentPage = pageNum; - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; await interaction.editReply({ embeds: [embedPresets.getSuccessEmbed("⌛ Please wait...")], components: [], - }) + }); - const { buttonsRow, embed } = await handleSetPage(state) + const { buttonsRow, embed } = await handleSetPage(state); await interaction.editReply({ embeds: [embed], components: [buttonsRow], - }) + }); } } diff --git a/src/interaction-handlers/pagination/models/tests/pagination-set-page.model.test.ts b/src/interaction-handlers/pagination/models/tests/pagination-set-page.model.test.ts index 5955db2..8b24146 100644 --- a/src/interaction-handlers/pagination/models/tests/pagination-set-page.model.test.ts +++ b/src/interaction-handlers/pagination/models/tests/pagination-set-page.model.test.ts @@ -1,106 +1,108 @@ -import { faker } from "@faker-js/faker" -import { container, InteractionHandlerStore, InteractionHandlerTypes } from "@sapphire/framework" -import { expect, describe, it, beforeAll, afterAll, jest, mock, beforeEach } from "bun:test" -import { FakerGenerator } from "../../../../lib/mock/faker.generator" -import { Mocker } from "../../../../lib/mock/mocker" -import { PaginationSetPageModal } from "../pagination-set-page.model" -import { PaginationInteractionCustomId } from "../../../../lib/types/enum/custom-ids.types" +import { faker } from "@faker-js/faker"; +import { container, InteractionHandlerStore, InteractionHandlerTypes } from "@sapphire/framework"; +import type { jest } from "bun:test"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, mock } from "bun:test"; + +import { FakerGenerator } from "../../../../lib/mock/faker.generator"; +import { Mocker } from "../../../../lib/mock/mocker"; +import { PaginationInteractionCustomId } from "../../../../lib/types/enum/custom-ids.types"; +import { PaginationSetPageModal } from "../pagination-set-page.model"; describe("Pagination Set Page Modal", () => { - let errorHandler: jest.Mock + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); describe("parse", async () => { it("invalid interaction id provided", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); const interaction = FakerGenerator.generateModalSubmitInteraction({ customId: "invalid_custom_id", - }) + }); - const result = await modal.parse(interaction) + const result = await modal.parse(interaction); - expect(result).toBe(modal.none()) - }) + expect(result).toBe(modal.none()); + }); it("invalid interaction id provided", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); - const customId = FakerGenerator.generateCustomId() + const customId = FakerGenerator.generateCustomId(); const interaction = FakerGenerator.generateModalSubmitInteraction({ - customId: customId, - }) + customId, + }); - const result = await modal.parse(interaction) + const result = await modal.parse(interaction); - expect(result).toBe(modal.none()) - }) + expect(result).toBe(modal.none()); + }); it("valid interaction id provided", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); - const dataStoreId = container.utilities.actionStore.set(mock()) + const dataStoreId = container.utilities.actionStore.set(mock()); - const userId = faker.number.int().toString() + const userId = faker.number.int().toString(); const customId = FakerGenerator.generateCustomId({ prefix: PaginationInteractionCustomId.PAGINATION_ACTION_SELECT_PAGE, userId, - ctx: { dataStoreId: dataStoreId }, - }) + ctx: { dataStoreId }, + }); const interaction = FakerGenerator.generateModalSubmitInteraction({ user: { id: userId, }, - customId: customId, - }) + customId, + }); - const result = await modal.parse(interaction) + const result = await modal.parse(interaction); - expect(result).not.toBe(modal.none()) - }) - }) + expect(result).not.toBe(modal.none()); + }); + }); describe("run", () => { it("should successfully navigate to a valid page", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); - const mockHandleSetPage = mock(async (state: any) => ({ + const mockHandleSetPage = mock(async (_state: any) => ({ embed: { title: "Page 3" }, buttonsRow: { components: [] }, - })) + })); const paginationData = FakerGenerator.generatePaginationData({ handleSetPage: mockHandleSetPage, - }) + }); - const deferUpdateMock = mock(async () => ({})) - const editReplyMock = mock(async () => ({})) - const getTextInputValueMock = mock(() => "3") + const deferUpdateMock = mock(async () => ({})); + const editReplyMock = mock(async () => ({})); + const getTextInputValueMock = mock(() => "3"); const interaction = FakerGenerator.generateModalSubmitInteraction({ deferUpdate: deferUpdateMock, @@ -108,119 +110,119 @@ describe("Pagination Set Page Modal", () => { fields: { getTextInputValue: getTextInputValueMock, }, - }) + }); - await modal.run(interaction, paginationData) + await modal.run(interaction, paginationData); - expect(deferUpdateMock).toHaveBeenCalled() - expect(getTextInputValueMock).toHaveBeenCalledWith("goToPageNumber") - expect(paginationData.state.currentPage).toBe(3) - expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state) - expect(editReplyMock).toHaveBeenCalledTimes(2) - }) + expect(deferUpdateMock).toHaveBeenCalled(); + expect(getTextInputValueMock).toHaveBeenCalledWith("goToPageNumber"); + expect(paginationData.state.currentPage).toBe(3); + expect(mockHandleSetPage).toHaveBeenCalledWith(paginationData.state); + expect(editReplyMock).toHaveBeenCalledTimes(2); + }); it("should throw error when input is not a number", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); - const paginationData = FakerGenerator.generatePaginationData() + const paginationData = FakerGenerator.generatePaginationData(); - const deferUpdateMock = mock(async () => ({})) - const getTextInputValueMock = mock(() => "not a number") + const deferUpdateMock = mock(async () => ({})); + const getTextInputValueMock = mock(() => "not a number"); const interaction = FakerGenerator.generateModalSubmitInteraction({ deferUpdate: deferUpdateMock, fields: { getTextInputValue: getTextInputValueMock, }, - }) + }); - expect(modal.run(interaction, paginationData)).rejects.toThrow("Not a number") - }) + expect(modal.run(interaction, paginationData)).rejects.toThrow("Not a number"); + }); it("should throw error when page number is zero", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); - const paginationData = FakerGenerator.generatePaginationData() + const paginationData = FakerGenerator.generatePaginationData(); - const deferUpdateMock = mock(async () => ({})) - const getTextInputValueMock = mock(() => "0") + const deferUpdateMock = mock(async () => ({})); + const getTextInputValueMock = mock(() => "0"); const interaction = FakerGenerator.generateModalSubmitInteraction({ deferUpdate: deferUpdateMock, fields: { getTextInputValue: getTextInputValueMock, }, - }) + }); - expect(modal.run(interaction, paginationData)).rejects.toThrow("Invalid page") - }) + expect(modal.run(interaction, paginationData)).rejects.toThrow("Invalid page"); + }); it("should throw error when page number is negative", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); - const paginationData = FakerGenerator.generatePaginationData() + const paginationData = FakerGenerator.generatePaginationData(); - const deferUpdateMock = mock(async () => ({})) - const getTextInputValueMock = mock(() => "-1") + const deferUpdateMock = mock(async () => ({})); + const getTextInputValueMock = mock(() => "-1"); const interaction = FakerGenerator.generateModalSubmitInteraction({ deferUpdate: deferUpdateMock, fields: { getTextInputValue: getTextInputValueMock, }, - }) + }); - expect(modal.run(interaction, paginationData)).rejects.toThrow("Invalid page") - }) + expect(modal.run(interaction, paginationData)).rejects.toThrow("Invalid page"); + }); it("should throw error when page number exceeds total pages", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); - const paginationData = FakerGenerator.generatePaginationData() + const paginationData = FakerGenerator.generatePaginationData(); - const deferUpdateMock = mock(async () => ({})) - const getTextInputValueMock = mock(() => "6") + const deferUpdateMock = mock(async () => ({})); + const getTextInputValueMock = mock(() => "6"); const interaction = FakerGenerator.generateModalSubmitInteraction({ deferUpdate: deferUpdateMock, fields: { getTextInputValue: getTextInputValueMock, }, - }) + }); - expect(modal.run(interaction, paginationData)).rejects.toThrow("Invalid page") - }) + expect(modal.run(interaction, paginationData)).rejects.toThrow("Invalid page"); + }); it("should show loading message before calling handleSetPage", async () => { const modal = new PaginationSetPageModal( { ...FakerGenerator.generateLoaderContext(), store: new InteractionHandlerStore() }, { interactionHandlerType: InteractionHandlerTypes.ModalSubmit }, - ) + ); - const mockHandleSetPage = mock(async (state: any) => ({ + const mockHandleSetPage = mock(async (_state: any) => ({ embed: { title: "Page 2" }, buttonsRow: { components: [] }, - })) + })); const paginationData = FakerGenerator.generatePaginationData({ handleSetPage: mockHandleSetPage, - }) + }); - const deferUpdateMock = mock(async () => ({})) - const editReplyMock = mock(async () => ({})) - const getTextInputValueMock = mock(() => "2") + const deferUpdateMock = mock(async () => ({})); + const editReplyMock = mock(async () => ({})); + const getTextInputValueMock = mock(() => "2"); const interaction = FakerGenerator.generateModalSubmitInteraction({ deferUpdate: deferUpdateMock, @@ -228,9 +230,9 @@ describe("Pagination Set Page Modal", () => { fields: { getTextInputValue: getTextInputValueMock, }, - }) + }); - await modal.run(interaction, paginationData) + await modal.run(interaction, paginationData); expect(editReplyMock).toHaveBeenNthCalledWith(1, { embeds: [ @@ -241,12 +243,12 @@ describe("Pagination Set Page Modal", () => { }), ], components: [], - }) + }); expect(editReplyMock).toHaveBeenNthCalledWith(2, { embeds: [expect.objectContaining({ title: "Page 2" })], components: [{ components: [] }], - }) - }) - }) -}) + }); + }); + }); +}); diff --git a/src/lib/configs/env.ts b/src/lib/configs/env.ts index 57c8ec7..8100700 100644 --- a/src/lib/configs/env.ts +++ b/src/lib/configs/env.ts @@ -1,50 +1,51 @@ -import path from "path" +import path from "node:path"; export interface IConfig { discord: { - token: string - } + token: string; + }; ids: { - newScoresChannel: string | undefined - beatmapsEventsChannel: string | undefined - } + newScoresChannel: string | undefined; + beatmapsEventsChannel: string | undefined; + }; sunrise: { - uri: string - } - environment: "prod" | "dev" + uri: string; + }; + environment: "prod" | "dev"; json: { emojis: { ranks: { - F: string - S: string - D: string - C: string - B: string - A: string - X: string - XH: string - SH: string - } - countSlidersIcon: string - countCirclesIcon: string - bpmIcon: string - totalLengthIcon: string - rankedStatus: string - } - } + F: string; + S: string; + D: string; + C: string; + B: string; + A: string; + X: string; + XH: string; + SH: string; + }; + countSlidersIcon: string; + countCirclesIcon: string; + bpmIcon: string; + totalLengthIcon: string; + rankedStatus: string; + }; + }; } -const requiredEnvVariables = ["DISCORD_TOKEN", "SUNRISE_URI"] -requiredEnvVariables.map((v) => { +const requiredEnvVariables = ["DISCORD_TOKEN", "SUNRISE_URI"]; +requiredEnvVariables.forEach((v) => { if (!process.env[v]) { - if (process.env.NODE_ENV === "test") return - throw new Error(`${v} is not provided in environment file!`) + if (process.env.NODE_ENV === "test") + return; + throw new Error(`${v} is not provided in environment file!`); } -}) +}); const env = ["prod", "dev"].includes(process.env.NODE_ENV ?? "") ? (process.env.NODE_ENV as any) - : "dev" + : "dev"; export const config: IConfig = { discord: { @@ -58,5 +59,6 @@ export const config: IConfig = { beatmapsEventsChannel: process.env["BEATMAPS_STATUSES_CHANNED_ID"] ?? undefined, }, environment: env, + // eslint-disable-next-line @typescript-eslint/no-require-imports, unicorn/prefer-module -- used to load JSON json: require(path.resolve(process.cwd(), "config", `${env}.json`)), -} +}; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 1ae3e2a..9411b02 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1 +1 @@ -export const EMPTY_CHAR = "\u200B" +export const EMPTY_CHAR = "\u200B"; diff --git a/src/lib/mock/faker.generator.ts b/src/lib/mock/faker.generator.ts index b339413..8f13355 100644 --- a/src/lib/mock/faker.generator.ts +++ b/src/lib/mock/faker.generator.ts @@ -1,83 +1,90 @@ -import { jest, mock } from "bun:test" - -import { +import { faker } from "@faker-js/faker"; +import type { Command } from "@sapphire/framework"; +import { CommandStore, container } from "@sapphire/framework"; +import type { DeepPartial } from "@sapphire/utilities"; +import { jest, mock } from "bun:test"; +import type { ApplicationCommand, - ApplicationCommandType, ButtonInteraction, + ModalSubmitInteraction, + User, +} from "discord.js"; +import { + ApplicationCommandType, InteractionType, Locale, - ModalSubmitInteraction, PermissionsBitField, - User, -} from "discord.js" +} from "discord.js"; -import { faker } from "@faker-js/faker" -import { Command, CommandStore, container } from "@sapphire/framework" -import type { DeepPartial } from "@sapphire/utilities" -import { buildCustomId } from "../utils/discord.util" -import type { PaginationStore } from "../types/store.types" -import { GameMode, BeatmapStatusWeb } from "../types/api" import type { - ScoreResponse, BeatmapResponse, + CountryCode, + ScoreResponse, UserResponse, UserStatsResponse, UserWithStats, -} from "../types/api" +} from "../types/api"; +import { BeatmapStatusWeb, GameMode } from "../types/api"; +import type { PaginationStore } from "../types/store.types"; +import { buildCustomId } from "../utils/discord.util"; function autoMock(base: Partial): T { return new Proxy(base as T, { get(target: any, prop: string | symbol) { if (prop in target) { - return target[prop] + return target[prop]; } - return mock(async (...args: any[]) => null) + return mock(async (..._args: any[]) => null); }, - }) as unknown as T + }) as unknown as T; } -const createBaseEntity = () => ({ - id: faker.string.uuid(), - createdAt: faker.date.past(), - createdTimestamp: Date.now(), -}) +function createBaseEntity() { + return { + id: faker.string.uuid(), + createdAt: faker.date.past(), + createdTimestamp: Date.now(), + }; +} -const createBaseInteraction = () => ({ - ...createBaseEntity(), - applicationId: faker.string.uuid(), - channelId: faker.string.uuid(), - guildId: faker.string.uuid(), - locale: Locale.French, -}) +function createBaseInteraction() { + return { + ...createBaseEntity(), + applicationId: faker.string.uuid(), + channelId: faker.string.uuid(), + guildId: faker.string.uuid(), + locale: Locale.French, + }; +} -export class FakerGenerator { - static generatePiece() { +export const FakerGenerator = { + generatePiece() { return { name: faker.string.alpha(10), store: container?.utilities?.store ?? {}, path: faker.system.filePath(), root: faker.system.directoryPath(), - } - } + }; + }, - static generateLoaderContext() { + generateLoaderContext() { return { name: faker.string.alpha(10), store: new CommandStore(), path: faker.system.filePath(), root: faker.system.directoryPath(), - } - } + }; + }, - static generateCustomId( + generateCustomId( options?: Partial<{ - prefix: string - userId: string + prefix: string; + userId: string; ctx: { - dataStoreId?: string | undefined - data?: string[] | undefined - } + dataStoreId?: string | undefined; + data?: string[] | undefined; + }; }>, ) { return buildCustomId( @@ -87,10 +94,10 @@ export class FakerGenerator { data: options?.ctx?.data ?? undefined, dataStoreId: options?.ctx?.dataStoreId ?? undefined, }, - ) - } + ); + }, - static generateInteraction( + generateInteraction( options?: DeepPartial, ): Command.ChatInputCommandInteraction { return autoMock({ @@ -106,10 +113,10 @@ export class FakerGenerator { ephemeral: faker.datatype.boolean(), replied: faker.datatype.boolean(), ...(options as any), - }) - } + }); + }, - static generateModalSubmitInteraction( + generateModalSubmitInteraction( options?: DeepPartial, ): ModalSubmitInteraction { return autoMock({ @@ -121,10 +128,10 @@ export class FakerGenerator { ephemeral: faker.datatype.boolean(), replied: faker.datatype.boolean(), ...(options as any), - }) - } + }); + }, - static generateButtonInteraction(options?: DeepPartial): ButtonInteraction { + generateButtonInteraction(options?: DeepPartial): ButtonInteraction { return autoMock({ ...createBaseInteraction(), user: options?.user ?? FakerGenerator.generateUser(), @@ -134,21 +141,21 @@ export class FakerGenerator { ephemeral: faker.datatype.boolean(), replied: faker.datatype.boolean(), ...(options as any), - }) - } + }); + }, - static withSubcommand( + withSubcommand( interaction: T, subcommand: string, ): T { - interaction.options.getSubcommand = jest.fn().mockReturnValue(subcommand) - interaction.options.getSubcommandGroup = jest.fn().mockReturnValue(null) + interaction.options.getSubcommand = jest.fn().mockReturnValue(subcommand); + interaction.options.getSubcommandGroup = jest.fn().mockReturnValue(null); - return interaction - } + return interaction; + }, - static generateCommand(options?: DeepPartial>): ApplicationCommand<{}> { - return autoMock>({ + generateCommand(options?: DeepPartial>): ApplicationCommand { + return autoMock>({ ...createBaseEntity(), applicationId: faker.string.uuid(), guildId: faker.string.uuid(), @@ -158,12 +165,12 @@ export class FakerGenerator { defaultMemberPermissions: new PermissionsBitField(PermissionsBitField.Flags.SendMessages), description: faker.lorem.sentence(), ...(options as any), - }) - } + }); + }, - static generateUser(options?: DeepPartial): User { - const userId = options?.id ?? faker.string.uuid() - const username = options?.username ?? faker.internet.username() + generateUser(options?: DeepPartial): User { + const userId = options?.id ?? faker.string.uuid(); + const username = options?.username ?? faker.internet.username(); return autoMock({ ...createBaseEntity(), @@ -180,13 +187,13 @@ export class FakerGenerator { displayAvatarURL: () => faker.internet.url(), toString: () => `<@${userId}>`, ...(options as any), - }) - } + }); + }, - static generatePaginationData(options?: DeepPartial): PaginationStore { + generatePaginationData(options?: DeepPartial): PaginationStore { return autoMock({ handleSetPage: - options?.handleSetPage ?? mock(async (state: any) => ({ embed: {}, buttonsRow: {} })), + options?.handleSetPage ?? mock(async (_state: any) => ({ embed: {}, buttonsRow: {} })), state: { pageSize: options?.state?.pageSize ?? 10, totalPages: options?.state?.totalPages ?? 5, @@ -194,14 +201,14 @@ export class FakerGenerator { ...(options?.state as any), }, ...(options as any), - }) - } + }); + }, - static generateOsuUser(options?: Partial): UserResponse { + generateOsuUser(options?: Partial): UserResponse { return { user_id: faker.number.int({ min: 1, max: 1000000 }), username: faker.internet.username(), - country_code: faker.location.countryCode(), + country_code: faker.location.countryCode() as CountryCode, avatar_url: "https://placehold.co/400x400", banner_url: "https://placehold.co/1200x300", register_date: new Date().toISOString(), @@ -213,11 +220,11 @@ export class FakerGenerator { user_status: "online", description: null, ...options, - } - } + }; + }, - static generateScore(options?: Partial): ScoreResponse { - const mockUser = options?.user ?? FakerGenerator.generateOsuUser() + generateScore(options?: Partial): ScoreResponse { + const mockUser = options?.user ?? FakerGenerator.generateOsuUser(); return { id: faker.number.int({ min: 1, max: 1000000 }), @@ -245,10 +252,10 @@ export class FakerGenerator { mods_int: 0, leaderboard_rank: null, ...options, - } - } + }; + }, - static generateBeatmap(options?: Partial): BeatmapResponse { + generateBeatmap(options?: Partial): BeatmapResponse { return { id: faker.number.int({ min: 1, max: 1000000 }), beatmapset_id: faker.number.int({ min: 1, max: 100000 }), @@ -284,11 +291,11 @@ export class FakerGenerator { creator_id: faker.number.int({ min: 1, max: 100000 }), beatmap_nominator_user: undefined, ...options, - } - } + }; + }, - static generateUserStats(options?: Partial): UserStatsResponse { - const userId = options?.user_id ?? faker.number.int({ min: 1, max: 1000000 }) + generateUserStats(options?: Partial): UserStatsResponse { + const userId = options?.user_id ?? faker.number.int({ min: 1, max: 1000000 }); return { user_id: userId, @@ -308,22 +315,22 @@ export class FakerGenerator { best_country_rank: faker.number.int({ min: 1, max: 5000 }), best_country_rank_date: new Date().toISOString(), ...options, - } - } + }; + }, - static generateUserWithStats(options?: { - user?: Partial - stats?: Partial + generateUserWithStats(options?: { + user?: Partial; + stats?: Partial; }): UserWithStats { - const user = FakerGenerator.generateOsuUser(options?.user) + const user = FakerGenerator.generateOsuUser(options?.user); const stats = FakerGenerator.generateUserStats({ user_id: user.user_id, ...options?.stats, - }) + }); return { user, stats, - } - } -} + }; + }, +}; diff --git a/src/lib/mock/mocker.ts b/src/lib/mock/mocker.ts index c5c7fa2..59a98d3 100644 --- a/src/lib/mock/mocker.ts +++ b/src/lib/mock/mocker.ts @@ -1,28 +1,30 @@ -import { jest, mock } from "bun:test" -import { Database } from "bun:sqlite" -import { getMigrations, migrate } from "bun-sqlite-migrations" -import path from "path" - -import { IntentsBitField } from "discord.js" -import { SapphireClient, LogLevel, container, Command, Events } from "@sapphire/framework" -import { SubcommandPluginEvents } from "@sapphire/plugin-subcommands" -import { UtilitiesStore } from "@sapphire/plugin-utilities-store" -import { faker } from "@faker-js/faker" - -import { ActionStoreUtility } from "../../utilities/action-store.utility" -import { EmbedPresetsUtility } from "../../utilities/embed-presets.utility" -import { PaginationUtility } from "../../utilities/pagination.utility" -import { FakerGenerator } from "./faker.generator" -import { config } from "../configs/env" +import path from "node:path"; + +import { faker } from "@faker-js/faker"; +import type { Command } from "@sapphire/framework"; +import { container, Events, LogLevel, SapphireClient } from "@sapphire/framework"; +import { SubcommandPluginEvents } from "@sapphire/plugin-subcommands"; +import { UtilitiesStore } from "@sapphire/plugin-utilities-store"; +import { Database } from "bun:sqlite"; +import type { jest } from "bun:test"; +import { mock } from "bun:test"; +import { getMigrations, migrate } from "bun-sqlite-migrations"; +import { IntentsBitField } from "discord.js"; + +import { ActionStoreUtility } from "../../utilities/action-store.utility"; +import { EmbedPresetsUtility } from "../../utilities/embed-presets.utility"; +import { PaginationUtility } from "../../utilities/pagination.utility"; +import { config } from "../configs/env"; +import { FakerGenerator } from "./faker.generator"; export class Mocker { static createSapphireClientInstance() { const client = new SapphireClient({ intents: new IntentsBitField().add(IntentsBitField.Flags.Guilds), logger: { level: LogLevel.Debug }, - }) + }); - container.client = client + container.client = client; container.logger = { trace: mock(), @@ -33,9 +35,9 @@ export class Mocker { warn: mock(), error: mock(), has: mock((level: LogLevel) => level === LogLevel.Debug), - } + }; - const store = new UtilitiesStore() + const store = new UtilitiesStore(); container.utilities = { ...container.utilities, store, @@ -43,37 +45,37 @@ export class Mocker { embedPresets: new EmbedPresetsUtility(FakerGenerator.generatePiece(), {}), pagination: new PaginationUtility(FakerGenerator.generatePiece(), {}), exposePiece(name, piece) { - store.set(name, piece) + store.set(name, piece); }, - } + }; container.config = { ...config, sunrise: { uri: "sunrise.example.com" }, - } + }; - this.createDatabaseInMemory() + this.createDatabaseInMemory(); } static async resetSapphireClientInstance() { - await container.client.destroy() - container.db.close() - this.createDatabaseInMemory() + await container.client.destroy(); + container.db.close(); + this.createDatabaseInMemory(); } static beforeEachCleanup(errorHandler: jest.Mock) { - errorHandler.mockClear() - Mocker.resetDatabase() + errorHandler.mockClear(); + Mocker.resetDatabase(); } private static resetDatabase() { const tables = container.db .query("SELECT name FROM sqlite_master WHERE type='table'") - .all() as { name: string }[] + .all() as Array<{ name: string }>; for (const table of tables) { if (table.name !== "sqlite_sequence") { - container.db.exec(`DELETE FROM ${table.name}`) + container.db.exec(`DELETE FROM ${table.name}`); } } } @@ -87,14 +89,14 @@ export class Mocker { store: container.utilities.store, }, {}, - ) + ); } static createErrorHandler() { - const errorHandler = mock() - container.client.on(Events.ChatInputCommandError, errorHandler) - container.client.on(SubcommandPluginEvents.ChatInputSubcommandError, errorHandler) - return errorHandler + const errorHandler = mock(); + container.client.on(Events.ChatInputCommandError, errorHandler); + container.client.on(SubcommandPluginEvents.ChatInputSubcommandError, errorHandler); + return errorHandler; } static mockApiRequest( @@ -103,18 +105,18 @@ export class Mocker { ) { mock.module(path.resolve(process.cwd(), "src", "lib", "types", "api"), () => ({ [mockedEndpointMethod]: implementation, - })) + })); } static mockApiRequests Promise>>(mocks: T) { - mock.module(path.resolve(process.cwd(), "src", "lib", "types", "api"), () => mocks) + mock.module(path.resolve(process.cwd(), "src", "lib", "types", "api"), () => mocks); } private static createDatabaseInMemory() { if (!container) { - throw new Error("Container is not initialized") + throw new Error("Container is not initialized"); } - container.db = new Database(":memory:") - migrate(container.db, getMigrations(path.resolve(process.cwd(), "data", "migrations"))) + container.db = new Database(":memory:"); + migrate(container.db, getMigrations(path.resolve(process.cwd(), "data", "migrations"))); } } diff --git a/src/lib/types/store.types.ts b/src/lib/types/store.types.ts index a84814d..832bf9f 100644 --- a/src/lib/types/store.types.ts +++ b/src/lib/types/store.types.ts @@ -1,13 +1,13 @@ -import type { EmbedBuilder, ActionRowBuilder, ButtonBuilder } from "discord.js" +import type { ActionRowBuilder, ButtonBuilder, EmbedBuilder } from "discord.js"; export interface PaginationStore { handleSetPage: (state: { pageSize: number; totalPages: number; currentPage: number }) => Promise<{ - embed: EmbedBuilder - buttonsRow: ActionRowBuilder - }> + embed: EmbedBuilder; + buttonsRow: ActionRowBuilder; + }>; state: { - pageSize: number - totalPages: number - currentPage: number - } + pageSize: number; + totalPages: number; + currentPage: number; + }; } diff --git a/src/lib/utils/command-logger.util.ts b/src/lib/utils/command-logger.util.ts index e2f3a71..fb99b9b 100644 --- a/src/lib/utils/command-logger.util.ts +++ b/src/lib/utils/command-logger.util.ts @@ -1,28 +1,28 @@ -import { cyan } from "colorette" -import { container } from "@sapphire/pieces" -import type { APIUser, Guild, User } from "discord.js" +import type { + ChatInputCommand, + ChatInputCommandErrorPayload, + ChatInputCommandSuccessPayload, +} from "@sapphire/framework"; +import { container } from "@sapphire/pieces"; import type { ChatInputCommandSubcommandMappingMethod, ChatInputSubcommandErrorPayload, ChatInputSubcommandSuccessPayload, Subcommand, -} from "@sapphire/plugin-subcommands" -import type { - ChatInputCommandSuccessPayload, - ChatInputCommandErrorPayload, - ChatInputCommand, -} from "@sapphire/framework" +} from "@sapphire/plugin-subcommands"; +import { cyan } from "colorette"; +import type { APIUser, Guild, User } from "discord.js"; function getShardInfo(id: number) { - return `[${cyan(id.toString())}]` + return `[${cyan(id.toString())}]`; } function getCommandInfo(command: ChatInputCommand | Subcommand) { - return cyan(command.name) + return cyan(command.name); } function getAuthorInfo(author: User | APIUser) { - return `${author.username} (${cyan(author.id)})` + return `${author.username} (${cyan(author.id)})`; } export function logCommand( @@ -33,32 +33,31 @@ export function logCommand( | ChatInputSubcommandErrorPayload, subcommand?: ChatInputCommandSubcommandMappingMethod | string, ): void { - let data: ReturnType - - let subcommandLog = "" + let subcommandLog = ""; if (subcommand) { if (typeof subcommand === "string") { - subcommandLog = ` ${cyan(subcommand)}` - } else { - subcommandLog = ` ${cyan(subcommand.name)}` + subcommandLog = ` ${cyan(subcommand)}`; + } + else { + subcommandLog = ` ${cyan(subcommand.name)}`; } } - data = getLoggerData(payload.interaction.guild, payload.interaction.user, payload.command) + const data = getLoggerData(payload.interaction.guild, payload.interaction.user, payload.command); container.logger.info( `User ${data.author} used /${data.commandName.replace(".command", "")}${subcommandLog}`, - ) + ); } function getLoggerData(guild: Guild | null, user: User, command: ChatInputCommand | Subcommand) { - const shard = getShardInfo(guild?.shardId ?? 0) - const commandName = getCommandInfo(command) - const author = getAuthorInfo(user) + const shard = getShardInfo(guild?.shardId ?? 0); + const commandName = getCommandInfo(command); + const author = getAuthorInfo(user); return { shard, commandName, author, - } + }; } diff --git a/src/lib/utils/decorator.util.ts b/src/lib/utils/decorator.util.ts index c430945..c5edb80 100644 --- a/src/lib/utils/decorator.util.ts +++ b/src/lib/utils/decorator.util.ts @@ -1,36 +1,36 @@ -import { createMethodDecorator } from "@sapphire/decorators" -import { Option } from "@sapphire/framework" +import { createMethodDecorator } from "@sapphire/decorators"; +import { Option } from "@sapphire/framework"; import type { ButtonInteraction, ModalSubmitInteraction, StringSelectMenuInteraction, -} from "discord.js" +} from "discord.js"; export function validCustomId(...customIds: string[]): MethodDecorator { return createMethodDecorator((_, __, descriptor: any) => { - const method = descriptor.value + const method = descriptor.value; if (typeof method !== "function") { - throw new Error("This can only be used on class methods") + throw new TypeError("This can only be used on class methods"); } descriptor.value = async function setValue( this: (...args: any[]) => any, interaction: ButtonInteraction | ModalSubmitInteraction | StringSelectMenuInteraction, ) { - if (!customIds.some((id) => interaction.customId.startsWith(id))) { - return Option.none + if (!customIds.some(id => interaction.customId.startsWith(id))) { + return Option.none; } - return method.call(this, interaction) - } as unknown as undefined - }) + return method.call(this, interaction); + } as unknown as undefined; + }); } export function ensureUsedBySameUser(): MethodDecorator { return createMethodDecorator((_, __, descriptor: any) => { - const method = descriptor.value + const method = descriptor.value; if (typeof method !== "function") { - throw new Error("This can only be used on class methods") + throw new TypeError("This can only be used on class methods"); } descriptor.value = async function setValue( @@ -38,10 +38,10 @@ export function ensureUsedBySameUser(): MethodDecorator { interaction: ButtonInteraction | ModalSubmitInteraction | StringSelectMenuInteraction, ) { if (interaction.customId.split(":")?.[1] !== interaction.user.id) { - return Option.none + return Option.none; } - return method.call(this, interaction) - } as unknown as undefined - }) + return method.call(this, interaction); + } as unknown as undefined; + }); } diff --git a/src/lib/utils/discord.util.ts b/src/lib/utils/discord.util.ts index 45583ba..ebd473d 100644 --- a/src/lib/utils/discord.util.ts +++ b/src/lib/utils/discord.util.ts @@ -2,22 +2,22 @@ export function buildCustomId( prefix: string, userId: string, ctx: { - dataStoreId?: string | undefined - data?: string[] | undefined + dataStoreId?: string | undefined; + data?: string[] | undefined; }, ): string { - const result = `${prefix}:${userId}:${ctx.dataStoreId}:${ctx.data?.join(",") ?? ""}` + const result = `${prefix}:${userId}:${ctx.dataStoreId}:${ctx.data?.join(",") ?? ""}`; if (result.length > 100) { - throw new Error("Custom IDs can only have a maximum length of 100") + throw new Error("Custom IDs can only have a maximum length of 100"); } - return result + return result; } export function parseCustomId(customId: string) { - const { 0: prefix, 1: userId, 2: dataStoreId, 3: data } = customId.split(":") + const { 0: prefix, 1: userId, 2: dataStoreId, 3: data } = customId.split(":"); if (!prefix || !userId) { - throw new Error("Couldn't parse custom ID: No prefix or userId") + throw new Error("Couldn't parse custom ID: No prefix or userId"); } return { @@ -27,5 +27,5 @@ export function parseCustomId(customId: string) { dataStoreId, data, }, - } + }; } diff --git a/src/lib/utils/fetch.util.ts b/src/lib/utils/fetch.util.ts index cb4aef4..fb2fa37 100644 --- a/src/lib/utils/fetch.util.ts +++ b/src/lib/utils/fetch.util.ts @@ -1,20 +1,21 @@ export async function tryToGetImage(imageUrl: string, placeholderUrl?: string) { - const haveAccess = await checkIfAccess(imageUrl) + const haveAccess = await checkIfAccess(imageUrl); if (haveAccess?.status !== 200) { return ( - placeholderUrl ?? - `https://github.com/SunriseCommunity/Sunset/blob/main/public/images/metadata.png?raw=true` - ) + placeholderUrl + ?? `https://github.com/SunriseCommunity/Sunset/blob/main/public/images/metadata.png?raw=true` + ); } - return imageUrl + return imageUrl; } async function checkIfAccess(url: string) { try { - const check = await fetch(url) - return check - } catch (error) { - return + const check = await fetch(url); + return check; + } + catch { + // ignore, return undefined } } diff --git a/src/lib/utils/interaction.util.ts b/src/lib/utils/interaction.util.ts index 839325b..51613ea 100644 --- a/src/lib/utils/interaction.util.ts +++ b/src/lib/utils/interaction.util.ts @@ -1,6 +1,7 @@ -import type { AnyInteraction } from "@sapphire/discord.js-utilities" -import { AutocompleteInteraction, Colors, EmbedBuilder } from "discord.js" -import type { EmbedPresetsUtility } from "../../utilities/embed-presets.utility" +import type { AnyInteraction } from "@sapphire/discord.js-utilities"; +import type { AutocompleteInteraction } from "discord.js"; + +import type { EmbedPresetsUtility } from "../../utilities/embed-presets.utility"; export function interactionError( embedPresets: EmbedPresetsUtility, @@ -10,9 +11,9 @@ export function interactionError( const payload = { embeds: [embedPresets.getErrorEmbed("Uh-oh!", message)], ephemeral: true, - } + }; return interaction.deferred || interaction.replied ? interaction.followUp(payload) - : interaction.reply(payload) + : interaction.reply(payload); } diff --git a/src/lib/utils/osu/emoji.util.ts b/src/lib/utils/osu/emoji.util.ts index de1603a..821473a 100644 --- a/src/lib/utils/osu/emoji.util.ts +++ b/src/lib/utils/osu/emoji.util.ts @@ -1,46 +1,46 @@ -import { config } from "../../configs/env" -import { BeatmapStatusWeb } from "../../types/api" +import { config } from "../../configs/env"; +import { BeatmapStatusWeb } from "../../types/api"; export function getScoreRankEmoji(rank: string) { switch (rank) { case "F": - return config.json.emojis.ranks.F + return config.json.emojis.ranks.F; case "D": - return config.json.emojis.ranks.D + return config.json.emojis.ranks.D; case "C": - return config.json.emojis.ranks.C + return config.json.emojis.ranks.C; case "B": - return config.json.emojis.ranks.B + return config.json.emojis.ranks.B; case "A": - return config.json.emojis.ranks.A + return config.json.emojis.ranks.A; case "S": - return config.json.emojis.ranks.S + return config.json.emojis.ranks.S; case "SH": - return config.json.emojis.ranks.SH + return config.json.emojis.ranks.SH; case "X": - return config.json.emojis.ranks.X + return config.json.emojis.ranks.X; case "XH": - return config.json.emojis.ranks.XH + return config.json.emojis.ranks.XH; } } export default function getBeatmapStatusIcon(status: BeatmapStatusWeb) { switch (status) { case BeatmapStatusWeb.LOVED: - return "❤️" + return "❤️"; case BeatmapStatusWeb.QUALIFIED: - return "☑️" + return "☑️"; case BeatmapStatusWeb.APPROVED: - return "✅" + return "✅"; case BeatmapStatusWeb.RANKED: - return config.json.emojis.rankedStatus + return config.json.emojis.rankedStatus; case BeatmapStatusWeb.GRAVEYARD: - return "🪦" + return "🪦"; case BeatmapStatusWeb.WIP: - return "🚧" + return "🚧"; case BeatmapStatusWeb.PENDING: - return "⌚" + return "⌚"; case BeatmapStatusWeb.UNKNOWN: - return "❓" + return "❓"; } } diff --git a/src/lib/utils/osu/star-rating.util.ts b/src/lib/utils/osu/star-rating.util.ts index 5f0b69b..da9f028 100644 --- a/src/lib/utils/osu/star-rating.util.ts +++ b/src/lib/utils/osu/star-rating.util.ts @@ -1,16 +1,17 @@ -import { GameMode, type BeatmapResponse } from "../../types/api" +import type { BeatmapResponse } from "../../types/api"; +import { GameMode } from "../../types/api"; export function getBeatmapStarRating(beatmap: BeatmapResponse, mode?: GameMode) { switch (mode ?? beatmap.mode) { case GameMode.STANDARD: - return beatmap.star_rating_osu.toFixed(2) + return beatmap.star_rating_osu.toFixed(2); case GameMode.TAIKO: - return beatmap.star_rating_taiko.toFixed(2) + return beatmap.star_rating_taiko.toFixed(2); case GameMode.CATCH_THE_BEAT: - return beatmap.star_rating_ctb.toFixed(2) + return beatmap.star_rating_ctb.toFixed(2); case GameMode.MANIA: - return beatmap.star_rating_mania.toFixed(2) + return beatmap.star_rating_mania.toFixed(2); default: - return -1 + return -1; } } diff --git a/src/lib/utils/tests/star-rating.util.spec.ts b/src/lib/utils/tests/star-rating.util.spec.ts index c01e2fe..11f1f70 100644 --- a/src/lib/utils/tests/star-rating.util.spec.ts +++ b/src/lib/utils/tests/star-rating.util.spec.ts @@ -1,49 +1,50 @@ -import { expect, describe, it, beforeAll, afterAll, beforeEach } from "bun:test" +import { beforeEach, describe, expect, it } from "bun:test"; -import { FakerGenerator } from "../../mock/faker.generator" -import { getBeatmapStarRating } from "../osu/star-rating.util" -import { GameMode, type BeatmapResponse } from "../../types/api" +import { FakerGenerator } from "../../mock/faker.generator"; +import type { BeatmapResponse } from "../../types/api"; +import { GameMode } from "../../types/api"; +import { getBeatmapStarRating } from "../osu/star-rating.util"; describe("star-rating.util", () => { describe("getBeatmapStarRating", () => { - let beatmap: BeatmapResponse = null! + let beatmap: BeatmapResponse = null!; beforeEach(() => { - beatmap = FakerGenerator.generateBeatmap() - }) + beatmap = FakerGenerator.generateBeatmap(); + }); it("should return correct star rating for STANDARD mode", () => { - const starRating = getBeatmapStarRating(beatmap, GameMode.STANDARD) + const starRating = getBeatmapStarRating(beatmap, GameMode.STANDARD); - expect(starRating).toBe(beatmap.star_rating_osu.toFixed(2)) - }) + expect(starRating).toBe(beatmap.star_rating_osu.toFixed(2)); + }); it("should return correct star rating for TAIKO mode", () => { - const starRating = getBeatmapStarRating(beatmap, GameMode.TAIKO) - expect(starRating).toBe(beatmap.star_rating_taiko.toFixed(2)) - }) + const starRating = getBeatmapStarRating(beatmap, GameMode.TAIKO); + expect(starRating).toBe(beatmap.star_rating_taiko.toFixed(2)); + }); it("should return correct star rating for CATCH_THE_BEAT mode", () => { - const starRating = getBeatmapStarRating(beatmap, GameMode.CATCH_THE_BEAT) - expect(starRating).toBe(beatmap.star_rating_ctb.toFixed(2)) - }) + const starRating = getBeatmapStarRating(beatmap, GameMode.CATCH_THE_BEAT); + expect(starRating).toBe(beatmap.star_rating_ctb.toFixed(2)); + }); it("should return correct star rating for MANIA mode", () => { - const starRating = getBeatmapStarRating(beatmap, GameMode.MANIA) - expect(starRating).toBe(beatmap.star_rating_mania.toFixed(2)) - }) + const starRating = getBeatmapStarRating(beatmap, GameMode.MANIA); + expect(starRating).toBe(beatmap.star_rating_mania.toFixed(2)); + }); it("should return -1 for unknown game mode", () => { - const starRating = getBeatmapStarRating(beatmap, "UNKNOWN_MODE" as GameMode) - expect(starRating).toBe(-1) - }) + const starRating = getBeatmapStarRating(beatmap, "UNKNOWN_MODE" as GameMode); + expect(starRating).toBe(-1); + }); it("should return star rating based on beatmap mode when no mode is provided", () => { - beatmap = FakerGenerator.generateBeatmap({ mode: GameMode.TAIKO }) + beatmap = FakerGenerator.generateBeatmap({ mode: GameMode.TAIKO }); - const starRating = getBeatmapStarRating(beatmap) + const starRating = getBeatmapStarRating(beatmap); - expect(starRating).toBe(beatmap.star_rating_taiko.toFixed(2)) - }) - }) -}) + expect(starRating).toBe(beatmap.star_rating_taiko.toFixed(2)); + }); + }); +}); diff --git a/src/lib/utils/text-conversion/get-duration.util.ts b/src/lib/utils/text-conversion/get-duration.util.ts index 6480ae8..402dbc2 100644 --- a/src/lib/utils/text-conversion/get-duration.util.ts +++ b/src/lib/utils/text-conversion/get-duration.util.ts @@ -1,4 +1,5 @@ -import { pluralize } from "./pluralize.util" +/* eslint-disable unused-imports/no-unused-vars -- used in eval */ +import { pluralize } from "./pluralize.util"; const declensions = { d: { @@ -21,24 +22,24 @@ const declensions = { someObjects: "seconds", manyObjects: "seconds", }, -} +}; export function getDuration(seconds: number) { - const d = Math.floor(seconds / (3600 * 24)) - const h = Math.floor((seconds % (3600 * 24)) / 3600) - const m = Math.floor((seconds % 3600) / 60) - const s = Math.floor(seconds % 60) + const d = Math.floor(seconds / (3600 * 24)); + const h = Math.floor((seconds % (3600 * 24)) / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); - const values = ["d", "h", "m", "s"] as const - let return_value = "" + const values = ["d", "h", "m", "s"] as const; + let return_value = ""; for (let i = 0; i < values.length; i++) { - const value = eval(values[i] ?? "d") + const value = eval(values[i] ?? "d"); - if (value > 0 || (seconds == 0 && i == 3)) { - const word = pluralize(value, "", declensions[values[i] ?? "d"]) - return_value += `${value} ${word} ` + if (value > 0 || (seconds === 0 && i === 3)) { + const word = pluralize(value, "", declensions[values[i] ?? "d"]); + return_value += `${value} ${word} `; } } - return return_value.trim() + return return_value.trim(); } diff --git a/src/lib/utils/text-conversion/number-with.util.ts b/src/lib/utils/text-conversion/number-with.util.ts index d67228b..e5c170f 100644 --- a/src/lib/utils/text-conversion/number-with.util.ts +++ b/src/lib/utils/text-conversion/number-with.util.ts @@ -1,3 +1,3 @@ export function numberWith(number: number | string, separator = " ") { - return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator) + return number.toString().replaceAll(/\B(?=(\d{3})+(?!\d))/g, separator); } diff --git a/src/lib/utils/text-conversion/pluralize.util.ts b/src/lib/utils/text-conversion/pluralize.util.ts index cf0770f..33d765f 100644 --- a/src/lib/utils/text-conversion/pluralize.util.ts +++ b/src/lib/utils/text-conversion/pluralize.util.ts @@ -1,17 +1,19 @@ interface Suffixes { - oneObject: string - someObjects?: string - manyObjects: string + oneObject: string; + someObjects?: string; + manyObjects: string; } export function pluralize(number: number, word: string, suffixes: Suffixes) { if (number <= 0) { - return word + suffixes.manyObjects - } else if (number % 10 === 1 && number % 100 !== 11) { - return word + suffixes.oneObject - } else if ([2, 3, 4].includes(number % 10) && ![12, 13, 14].includes(number % 100)) { - return word + (suffixes.someObjects ? suffixes.someObjects : suffixes.manyObjects) - } else { - return word + suffixes.manyObjects + return word + suffixes.manyObjects; } + else if (number % 10 === 1 && number % 100 !== 11) { + return word + suffixes.oneObject; + } + else if (!([2, 3, 4].includes(number % 10) && ![12, 13, 14].includes(number % 100))) { + return word + suffixes.manyObjects; + } + + return word + (suffixes.someObjects ?? suffixes.manyObjects); } diff --git a/src/lib/utils/text-conversion/seconds-to.util.ts b/src/lib/utils/text-conversion/seconds-to.util.ts index 6af5af6..c838614 100644 --- a/src/lib/utils/text-conversion/seconds-to.util.ts +++ b/src/lib/utils/text-conversion/seconds-to.util.ts @@ -1,9 +1,9 @@ -export const secondsToMinutes = (justSeconds: any, options: any) => { - const minutes = Math.floor(justSeconds / 60) - const seconds = justSeconds % 60 +export function secondsToMinutes(totalSeconds: number): string { + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; function padTo2Digits(num: any) { - return num.toString().padStart(2, "0") + return num.toString().padStart(2, "0"); } - const result = `${padTo2Digits(minutes)}:${padTo2Digits(seconds)}` - return result + const result = `${padTo2Digits(minutes)}:${padTo2Digits(seconds)}`; + return result; } diff --git a/src/listeners/commands/chat-input-command-denied.listener.ts b/src/listeners/commands/chat-input-command-denied.listener.ts index 2f4d4b3..833928d 100644 --- a/src/listeners/commands/chat-input-command-denied.listener.ts +++ b/src/listeners/commands/chat-input-command-denied.listener.ts @@ -1,7 +1,7 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Events, Identifiers, type ChatInputCommandDeniedPayload } from "@sapphire/framework" -import { Listener, UserError } from "@sapphire/framework" -import { MessageFlags, time } from "discord.js" +import { ApplyOptions } from "@sapphire/decorators"; +import type { ChatInputCommandDeniedPayload, UserError } from "@sapphire/framework"; +import { Events, Identifiers, Listener } from "@sapphire/framework"; +import { MessageFlags, time } from "discord.js"; @ApplyOptions({ name: Events.ChatInputCommandDenied, @@ -11,14 +11,15 @@ export class ChatInputCommandDeniedListener extends Listener { { context, message: content, identifier }: UserError, { interaction }: ChatInputCommandDeniedPayload, ) { - if (Reflect.get(Object(context), "silent")) return + if (Reflect.get(new Object(context), "silent")) + return; - await interaction.deferReply({ flags: [MessageFlags.Ephemeral] }) + await interaction.deferReply({ flags: [MessageFlags.Ephemeral] }); if (identifier === Identifiers.PreconditionCooldown) { - const remaining = Object(context).remaining + const { remaining } = new Object(context); - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; const cooldownEmbed = embedPresets.getErrorEmbed( "Hold up!", @@ -26,11 +27,11 @@ export class ChatInputCommandDeniedListener extends Listener { Math.floor((Date.now() + remaining) / 1000), "R", )}`, - ) + ); - return interaction.editReply({ embeds: [cooldownEmbed] }) + return interaction.editReply({ embeds: [cooldownEmbed] }); } - return interaction.editReply({ content }) + return interaction.editReply({ content }); } } diff --git a/src/listeners/commands/chat-input-command-error.listener.ts b/src/listeners/commands/chat-input-command-error.listener.ts index d48da66..292ad3c 100644 --- a/src/listeners/commands/chat-input-command-error.listener.ts +++ b/src/listeners/commands/chat-input-command-error.listener.ts @@ -1,25 +1,28 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Events, type ChatInputCommandErrorPayload } from "@sapphire/framework" -import { Listener } from "@sapphire/framework" -import { Subcommand } from "@sapphire/plugin-subcommands" -import { logCommand } from "../../lib/utils/command-logger.util" -import { ExtendedError } from "../../lib/extended-error" -import { interactionError } from "../../lib/utils/interaction.util" +import { ApplyOptions } from "@sapphire/decorators"; +import type { ChatInputCommandErrorPayload } from "@sapphire/framework"; +import { Events, Listener } from "@sapphire/framework"; +import { Subcommand } from "@sapphire/plugin-subcommands"; + +import { ExtendedError } from "../../lib/extended-error"; +import { logCommand } from "../../lib/utils/command-logger.util"; +import { interactionError } from "../../lib/utils/interaction.util"; @ApplyOptions({ name: Events.ChatInputCommandError, }) export class ChatInputCommandErrorListener extends Listener { public override run(error: Error, payload: ChatInputCommandErrorPayload) { - if (payload.command instanceof Subcommand) return + if (payload.command instanceof Subcommand) + return; - logCommand(payload) + logCommand(payload); if (error instanceof ExtendedError) { - const { embedPresets } = this.container.utilities - interactionError(embedPresets, payload.interaction, error.message) - } else { - this.container.logger.error(error) + const { embedPresets } = this.container.utilities; + interactionError(embedPresets, payload.interaction, error.message); + } + else { + this.container.logger.error(error); } } } diff --git a/src/listeners/commands/chat-input-command-success.listener.ts b/src/listeners/commands/chat-input-command-success.listener.ts index 90ba4a8..deeb56e 100644 --- a/src/listeners/commands/chat-input-command-success.listener.ts +++ b/src/listeners/commands/chat-input-command-success.listener.ts @@ -1,16 +1,18 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Events, type ChatInputCommandSuccessPayload } from "@sapphire/framework" -import { Listener } from "@sapphire/framework" -import { Subcommand } from "@sapphire/plugin-subcommands" -import { logCommand } from "../../lib/utils/command-logger.util" +import { ApplyOptions } from "@sapphire/decorators"; +import type { ChatInputCommandSuccessPayload } from "@sapphire/framework"; +import { Events, Listener } from "@sapphire/framework"; +import { Subcommand } from "@sapphire/plugin-subcommands"; + +import { logCommand } from "../../lib/utils/command-logger.util"; @ApplyOptions({ name: Events.ChatInputCommandSuccess, }) export class ChatInputCommandSuccessListener extends Listener { public override run(payload: ChatInputCommandSuccessPayload) { - if (payload.command instanceof Subcommand) return + if (payload.command instanceof Subcommand) + return; - logCommand(payload) + logCommand(payload); } } diff --git a/src/listeners/commands/chat-input-subcommand-error.listener.ts b/src/listeners/commands/chat-input-subcommand-error.listener.ts index de9cba4..4af6dc2 100644 --- a/src/listeners/commands/chat-input-subcommand-error.listener.ts +++ b/src/listeners/commands/chat-input-subcommand-error.listener.ts @@ -1,25 +1,27 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Listener } from "@sapphire/framework" +import { ApplyOptions } from "@sapphire/decorators"; +import { Listener } from "@sapphire/framework"; +import type { ChatInputSubcommandErrorPayload } from "@sapphire/plugin-subcommands"; import { SubcommandPluginEvents, - type ChatInputSubcommandErrorPayload, -} from "@sapphire/plugin-subcommands" -import { logCommand } from "../../lib/utils/command-logger.util" -import { ExtendedError } from "../../lib/extended-error" -import { interactionError } from "../../lib/utils/interaction.util" +} from "@sapphire/plugin-subcommands"; + +import { ExtendedError } from "../../lib/extended-error"; +import { logCommand } from "../../lib/utils/command-logger.util"; +import { interactionError } from "../../lib/utils/interaction.util"; @ApplyOptions({ name: SubcommandPluginEvents.ChatInputSubcommandError, }) export class ChatInputSubcommandErrorListener extends Listener { public override run(error: Error, payload: ChatInputSubcommandErrorPayload) { - logCommand(payload, payload.matchedSubcommandMapping.name) + logCommand(payload, payload.matchedSubcommandMapping.name); if (error instanceof ExtendedError) { - const { embedPresets } = this.container.utilities - interactionError(embedPresets, payload.interaction, error.message) - } else { - this.container.logger.error(error) + const { embedPresets } = this.container.utilities; + interactionError(embedPresets, payload.interaction, error.message); + } + else { + this.container.logger.error(error); } } } diff --git a/src/listeners/commands/chat-input-subcommand-success.listener.ts b/src/listeners/commands/chat-input-subcommand-success.listener.ts index fcc0e2f..8ae6af4 100644 --- a/src/listeners/commands/chat-input-subcommand-success.listener.ts +++ b/src/listeners/commands/chat-input-subcommand-success.listener.ts @@ -1,12 +1,12 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Listener } from "@sapphire/framework" +import { ApplyOptions } from "@sapphire/decorators"; +import { Listener } from "@sapphire/framework"; +import type { ChatInputCommandSubcommandMappingMethod, ChatInputSubcommandSuccessPayload } from "@sapphire/plugin-subcommands"; import { SubcommandPluginEvents, - type ChatInputCommandSubcommandMappingMethod, - type ChatInputSubcommandSuccessPayload, -} from "@sapphire/plugin-subcommands" -import { logCommand } from "../../lib/utils/command-logger.util" -import type { Interaction } from "discord.js" +} from "@sapphire/plugin-subcommands"; +import type { Interaction } from "discord.js"; + +import { logCommand } from "../../lib/utils/command-logger.util"; @ApplyOptions({ name: SubcommandPluginEvents.ChatInputSubcommandSuccess, @@ -17,6 +17,6 @@ export class ChatInputSubcommandSuccessListener extends Listener { subcommand: ChatInputCommandSubcommandMappingMethod, payload: ChatInputSubcommandSuccessPayload, ) { - logCommand(payload, subcommand) + logCommand(payload, subcommand); } } diff --git a/src/listeners/interaction/interactionHandlerError.ts b/src/listeners/interaction/interaction-handler-error.listener.ts similarity index 60% rename from src/listeners/interaction/interactionHandlerError.ts rename to src/listeners/interaction/interaction-handler-error.listener.ts index 291efe3..e8fdf79 100644 --- a/src/listeners/interaction/interactionHandlerError.ts +++ b/src/listeners/interaction/interaction-handler-error.listener.ts @@ -1,22 +1,23 @@ -import { Listener } from "@sapphire/framework" -import { Events, type InteractionHandlerError } from "@sapphire/framework" -import { ApplyOptions } from "@sapphire/decorators" -import { isAnyInteractableInteraction } from "@sapphire/discord.js-utilities" -import { interactionError } from "../../lib/utils/interaction.util" -import { ExtendedError } from "../../lib/extended-error" +import { ApplyOptions } from "@sapphire/decorators"; +import { isAnyInteractableInteraction } from "@sapphire/discord.js-utilities"; +import type { InteractionHandlerError } from "@sapphire/framework"; +import { Events, Listener } from "@sapphire/framework"; + +import { ExtendedError } from "../../lib/extended-error"; +import { interactionError } from "../../lib/utils/interaction.util"; @ApplyOptions({ name: Events.InteractionHandlerError }) export class InteractionHandlerErrorEvent extends Listener { public override async run(error: Error, { interaction, handler }: InteractionHandlerError) { if (isAnyInteractableInteraction(interaction)) { - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; if (error instanceof ExtendedError) { - interactionError(embedPresets, interaction, error.message) - return - } else { - interactionError(embedPresets, interaction, "Something went wrong... Sorry!") - this.container.logger.error(handler.name, error) + interactionError(embedPresets, interaction, error.message); + } + else { + interactionError(embedPresets, interaction, "Something went wrong... Sorry!"); + this.container.logger.error(handler.name, error); } } } diff --git a/src/listeners/ready.listener.ts b/src/listeners/ready.listener.ts index 993e306..15a0623 100644 --- a/src/listeners/ready.listener.ts +++ b/src/listeners/ready.listener.ts @@ -1,16 +1,17 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Events, Listener } from "@sapphire/framework" -import { ActivityType, type Client } from "discord.js" +import { ApplyOptions } from "@sapphire/decorators"; +import { Events, Listener } from "@sapphire/framework"; +import type { Client } from "discord.js"; +import { ActivityType } from "discord.js"; @ApplyOptions({ event: Events.ClientReady, once: true }) export class ReadyListener extends Listener { public run(client: Client) { - const { username, id } = client.user! + const { username, id } = client.user!; - this.container.logger.info(`⛅ Successfully logged in as ${username} (${id})`) + this.container.logger.info(`⛅ Successfully logged in as ${username} (${id})`); this.container.client.user?.setPresence({ activities: [{ type: ActivityType.Playing, name: "osu!" }], status: "online", - }) + }); } } diff --git a/src/listeners/websocket/custom-beatmap-status-change.listener.ts b/src/listeners/websocket/custom-beatmap-status-change.listener.ts index a407f79..3401506 100644 --- a/src/listeners/websocket/custom-beatmap-status-change.listener.ts +++ b/src/listeners/websocket/custom-beatmap-status-change.listener.ts @@ -1,6 +1,8 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { container, Listener } from "@sapphire/framework" -import { WebSocketEventType, type CustomBeatmapStatusChangeResponse } from "../../lib/types/api" +import { ApplyOptions } from "@sapphire/decorators"; +import { container, Listener } from "@sapphire/framework"; + +import type { CustomBeatmapStatusChangeResponse } from "../../lib/types/api"; +import { WebSocketEventType } from "../../lib/types/api"; @ApplyOptions({ event: WebSocketEventType.CUSTOM_BEATMAP_STATUS_CHANGED, @@ -8,8 +10,9 @@ import { WebSocketEventType, type CustomBeatmapStatusChangeResponse } from "../. }) export class CustomBeatmapStatusChangeListener extends Listener { public async run(data: CustomBeatmapStatusChangeResponse) { - const { beatmapsEventsChannel } = this.container.config.ids - if (!beatmapsEventsChannel) return + const { beatmapsEventsChannel } = this.container.config.ids; + if (!beatmapsEventsChannel) + return; const beatmapsChannel = await this.container.client.channels .fetch(beatmapsEventsChannel.toString()) @@ -17,20 +20,20 @@ export class CustomBeatmapStatusChangeListener extends Listener { this.container.client.logger.error( "CustomBeatmapStatusChangeListener: Couldn't fetch beatmaps event channel", ), - ) + ); if (!beatmapsChannel || !beatmapsChannel.isSendable()) { this.container.client.logger.error( `CustomBeatmapStatusChangeListener: Can't send custom beatmap status change event. Check if beatmaps channel ${beatmapsEventsChannel} exists.`, - ) - return + ); + return; } - const { embedPresets } = this.container.utilities - const beatmapStatusChangeEventEmbed = await embedPresets.getCustomBeatmapStatusChangeEmbed(data) + const { embedPresets } = this.container.utilities; + const beatmapStatusChangeEventEmbed = await embedPresets.getCustomBeatmapStatusChangeEmbed(data); await beatmapsChannel.send({ embeds: [beatmapStatusChangeEventEmbed], - }) + }); } } diff --git a/src/listeners/websocket/new-score-submission.listener.ts b/src/listeners/websocket/new-score-submission.listener.ts index 76c5a7c..aefc89f 100644 --- a/src/listeners/websocket/new-score-submission.listener.ts +++ b/src/listeners/websocket/new-score-submission.listener.ts @@ -1,7 +1,9 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { container, Listener } from "@sapphire/framework" -import { getBeatmapById, WebSocketEventType, type ScoreResponse } from "../../lib/types/api" -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js" +import { ApplyOptions } from "@sapphire/decorators"; +import { container, Listener } from "@sapphire/framework"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; + +import type { ScoreResponse } from "../../lib/types/api"; +import { getBeatmapById, WebSocketEventType } from "../../lib/types/api"; @ApplyOptions({ event: WebSocketEventType.NEW_SCORE_SUBMITTED, @@ -9,12 +11,13 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js" }) export class NewScoreSubmissionListener extends Listener { public async run(score: ScoreResponse) { - const { newScoresChannel } = this.container.config.ids - if (!newScoresChannel) return + const { newScoresChannel } = this.container.config.ids; + if (!newScoresChannel) + return; this.container.client.logger.info( `NewScoreSubmissionListener: New score (id: ${score.id}) submitted, trying to send embed to scores channel.`, - ) + ); const scoresChannel = await this.container.client.channels .fetch(newScoresChannel.toString()) @@ -22,41 +25,41 @@ export class NewScoreSubmissionListener extends Listener { this.container.client.logger.error( "NewScoreSubmissionListener: Couldn't fetch scores channel", ), - ) + ); if (!scoresChannel || !scoresChannel.isSendable()) { this.container.client.logger.error( `NewScoreSubmissionListener: Can't send new score embed. Check if scores channel ${newScoresChannel} exists.`, - ) - return + ); + return; } const beatmap = await getBeatmapById({ path: { id: score.beatmap_id, }, - }) + }); if (!beatmap || beatmap.error) { this.container.client.logger.error( `NewScoreSubmissionListener: Couldn't fetch score's (id: ${score.id}) beatmap (id: ${score.beatmap_id}).`, - ) - return + ); + return; } - const { embedPresets } = this.container.utilities - const scoreEmbed = await embedPresets.getScoreEmbed(score, beatmap.data, true) + const { embedPresets } = this.container.utilities; + const scoreEmbed = await embedPresets.getScoreEmbed(score, beatmap.data, true); const buttons = new ActionRowBuilder().addComponents( new ButtonBuilder() .setURL(`https://${this.container.config.sunrise.uri}/score/${score.id}`) .setLabel("View score online") .setStyle(ButtonStyle.Link), - ) + ); await scoresChannel.send({ embeds: [scoreEmbed], components: [buttons], - }) + }); } } diff --git a/src/subcommands/osu/link.subcommand.ts b/src/subcommands/osu/link.subcommand.ts index a70b7b8..3f3bb12 100644 --- a/src/subcommands/osu/link.subcommand.ts +++ b/src/subcommands/osu/link.subcommand.ts @@ -1,54 +1,56 @@ -import { getUserSearch } from "../../lib/types/api" -import { bold, type SlashCommandSubcommandBuilder } from "discord.js" -import type { OsuCommand } from "../../commands/osu.command" -import type { Subcommand } from "@sapphire/plugin-subcommands" -import { ExtendedError } from "../../lib/extended-error" +import type { Subcommand } from "@sapphire/plugin-subcommands"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; +import { bold } from "discord.js"; + +import type { OsuCommand } from "../../commands/osu.command"; +import { ExtendedError } from "../../lib/extended-error"; +import { getUserSearch } from "../../lib/types/api"; export function addLinkSubcommand(command: SlashCommandSubcommandBuilder) { return command .setName("link") .setDescription("Link your osu!sunrise profile") - .addStringOption((o) => + .addStringOption(o => o.setName("username").setDescription("Your username on the server").setRequired(true), - ) + ); } export async function chatInputRunLinkSubcommand( this: OsuCommand, interaction: Subcommand.ChatInputCommandInteraction, ) { - await interaction.deferReply() + await interaction.deferReply(); - const userUsernameOption = interaction.options.getString("username", true) + const userUsernameOption = interaction.options.getString("username", true); const userSearchResponse = await getUserSearch({ query: { limit: 1, page: 1, query: userUsernameOption }, - }) + }); if (userSearchResponse.error || userSearchResponse.data.length <= 0) { throw new ExtendedError( - userSearchResponse?.error?.detail || - userSearchResponse?.error?.title || - "Couldn't fetch user!", - ) + userSearchResponse?.error?.detail + || userSearchResponse?.error?.title + || "Couldn't fetch user!", + ); } - const user = userSearchResponse.data[0]! + const user = userSearchResponse.data[0]!; - const { db } = this.container + const { db } = this.container; const insertConnection = db.prepare( "INSERT OR REPLACE INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2);", - ) + ); insertConnection.run({ $1: interaction.user.id, $2: user.user_id, - }) + }); - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; return await interaction.editReply({ embeds: [embedPresets.getSuccessEmbed(`🙂 You are now ${bold(user.username)}!`)], - }) + }); } diff --git a/src/subcommands/osu/profile.subcommand.ts b/src/subcommands/osu/profile.subcommand.ts index 8e99d74..dc26538 100644 --- a/src/subcommands/osu/profile.subcommand.ts +++ b/src/subcommands/osu/profile.subcommand.ts @@ -1,94 +1,95 @@ -import { GameMode, getUserByIdByMode, getUserSearch } from "../../lib/types/api" -import type { SlashCommandSubcommandBuilder } from "discord.js" -import type { OsuCommand } from "../../commands/osu.command" -import type { Subcommand } from "@sapphire/plugin-subcommands" -import { ExtendedError } from "../../lib/extended-error" +import type { Subcommand } from "@sapphire/plugin-subcommands"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; + +import type { OsuCommand } from "../../commands/osu.command"; +import { ExtendedError } from "../../lib/extended-error"; +import { GameMode, getUserByIdByMode, getUserSearch } from "../../lib/types/api"; export function addProfileSubcommand(command: SlashCommandSubcommandBuilder) { return command .setName("profile") .setDescription("Check user's profile") - .addStringOption((o) => o.setName("username").setDescription("User's username")) - .addUserOption((o) => + .addStringOption(o => o.setName("username").setDescription("User's username")) + .addUserOption(o => o.setName("discord").setDescription("Show users profile if he linked any"), ) - .addNumberOption((option) => option.setName("id").setDescription("User's id")) - .addStringOption((option) => + .addNumberOption(option => option.setName("id").setDescription("User's id")) + .addStringOption(option => option .setName("gamemode") .setDescription("Select gamemode") .setChoices( - Object.values(GameMode).map((mode) => ({ + Object.values(GameMode).map(mode => ({ name: mode.toString(), value: mode.toString(), })), ), - ) + ); } export async function chatInputRunProfileSubcommand( this: OsuCommand, interaction: Subcommand.ChatInputCommandInteraction, ) { - await interaction.deferReply() + await interaction.deferReply(); - let userIdOption = interaction.options.getNumber("id") - const userUsernameOption = interaction.options.getString("username") - const userDiscordOption = interaction.options.getUser("discord") + let userIdOption = interaction.options.getNumber("id"); + const userUsernameOption = interaction.options.getString("username"); + const userDiscordOption = interaction.options.getUser("discord"); - const gamemodeOption = interaction.options.getString("gamemode") as GameMode | null + const gamemodeOption = interaction.options.getString("gamemode") as GameMode | null; - let userResponse = null + let userResponse = null; - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; if (userUsernameOption) { const userSearchResponse = await getUserSearch({ query: { limit: 1, page: 1, query: userUsernameOption }, - }) + }); if (userSearchResponse.error || userSearchResponse.data.length <= 0) { throw new ExtendedError( - userSearchResponse?.error?.detail || - userSearchResponse?.error?.title || - "❓ I couldn't find user with such username", - ) + userSearchResponse?.error?.detail + || userSearchResponse?.error?.title + || "❓ I couldn't find user with such username", + ); } - userIdOption = userSearchResponse.data[0]?.user_id ?? null + userIdOption = userSearchResponse.data[0]?.user_id ?? null; } if (userIdOption && userResponse == null) { userResponse = await getUserByIdByMode({ path: { id: userIdOption, mode: gamemodeOption ?? GameMode.STANDARD }, - }) + }); } if (userResponse == null) { - const { db } = this.container + const { db } = this.container; const row = db.query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1").get({ $1: userDiscordOption ? userDiscordOption.id : interaction.user.id, - }) as null | { osu_user_id: number } + }) as null | { osu_user_id: number }; if (!row || !row.osu_user_id) { - throw new ExtendedError(`❓ Provided user didn't link their osu!sunrise account`) + throw new ExtendedError(`❓ Provided user didn't link their osu!sunrise account`); } userResponse = await getUserByIdByMode({ path: { id: row.osu_user_id, mode: gamemodeOption ?? GameMode.STANDARD }, - }) + }); } if (!userResponse || userResponse.error) { throw new ExtendedError( userResponse?.error?.detail || userResponse?.error?.title || "Couldn't fetch requested user!", - ) + ); } - const { user, stats } = userResponse.data + const { user, stats } = userResponse.data; - const userEmbed = await embedPresets.getUserEmbed(user, stats!) + const userEmbed = await embedPresets.getUserEmbed(user, stats!); - await interaction.editReply({ embeds: [userEmbed] }) + await interaction.editReply({ embeds: [userEmbed] }); } diff --git a/src/subcommands/osu/recent-score.subcommand.ts b/src/subcommands/osu/recent-score.subcommand.ts index 81bb18a..21f3f79 100644 --- a/src/subcommands/osu/recent-score.subcommand.ts +++ b/src/subcommands/osu/recent-score.subcommand.ts @@ -1,71 +1,71 @@ +import type { Subcommand } from "@sapphire/plugin-subcommands"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, +} from "discord.js"; + +import type { OsuCommand } from "../../commands/osu.command"; +import { ExtendedError } from "../../lib/extended-error"; import { GameMode, getBeatmapById, - getScoreById, getUserByIdScores, getUserSearch, ScoreTableType, -} from "../../lib/types/api" -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - type SlashCommandSubcommandBuilder, -} from "discord.js" -import type { OsuCommand } from "../../commands/osu.command" -import type { Subcommand } from "@sapphire/plugin-subcommands" -import { ExtendedError } from "../../lib/extended-error" +} from "../../lib/types/api"; export function addRecentScoreSubcommand(command: SlashCommandSubcommandBuilder) { return command .setName("rs") .setDescription("Check users recent score") - .addStringOption((o) => + .addStringOption(o => o.setName("username").setDescription("User's username").setRequired(false), ) - .addUserOption((o) => + .addUserOption(o => o.setName("discord").setDescription("Show users profile if he linked any").setRequired(false), ) - .addStringOption((o) => + .addStringOption(o => o .setName("gamemode") .setDescription("Select gamemode") .setRequired(false) .setChoices( - Object.values(GameMode).map((mode) => ({ + Object.values(GameMode).map(mode => ({ name: mode.toString(), value: mode.toString(), })), ), - ) + ); } export async function chatInputRunRecentScoreSubcommand( this: OsuCommand, interaction: Subcommand.ChatInputCommandInteraction, ) { - await interaction.deferReply() + await interaction.deferReply(); - const userUsernameOption = interaction.options.getString("username") - const userDiscordOption = interaction.options.getUser("discord") + const userUsernameOption = interaction.options.getString("username"); + const userDiscordOption = interaction.options.getUser("discord"); - const gamemodeOption = interaction.options.getString("gamemode") as GameMode | null + const gamemodeOption = interaction.options.getString("gamemode") as GameMode | null; - let recentScoreResponse = null + let recentScoreResponse = null; - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; if (userUsernameOption) { const userSearchResponse = await getUserSearch({ query: { limit: 1, page: 1, query: userUsernameOption }, - }) + }); if (userSearchResponse.error || userSearchResponse.data.length <= 0) { throw new ExtendedError( - userSearchResponse?.error?.detail || - userSearchResponse?.error?.title || - "❓ I couldn't find user with such username", - ) + userSearchResponse?.error?.detail + || userSearchResponse?.error?.title + || "❓ I couldn't find user with such username", + ); } recentScoreResponse = await getUserByIdScores({ @@ -78,18 +78,18 @@ export async function chatInputRunRecentScoreSubcommand( page: 1, limit: 1, }, - }) + }); } if (recentScoreResponse == null) { - const { db } = this.container + const { db } = this.container; const row = db.query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1").get({ $1: userDiscordOption ? userDiscordOption.id : interaction.user.id, - }) as null | { osu_user_id: number } + }) as null | { osu_user_id: number }; if (!row || !row.osu_user_id) { - throw new ExtendedError(`❓ Provided user didn't link their osu!sunrise account`) + throw new ExtendedError(`❓ Provided user didn't link their osu!sunrise account`); } recentScoreResponse = await getUserByIdScores({ @@ -102,47 +102,47 @@ export async function chatInputRunRecentScoreSubcommand( page: 1, limit: 1, }, - }) + }); } if (!recentScoreResponse || recentScoreResponse.error) { throw new ExtendedError( - recentScoreResponse?.error?.detail || - recentScoreResponse?.error?.title || - "Couldn't fetch requested user's recent score!", - ) + recentScoreResponse?.error?.detail + || recentScoreResponse?.error?.title + || "Couldn't fetch requested user's recent score!", + ); } if (recentScoreResponse.data.scores.length <= 0) { - throw new ExtendedError("This user has no recent scores") + throw new ExtendedError("This user has no recent scores"); } - const score = recentScoreResponse.data.scores[0]! + const score = recentScoreResponse.data.scores[0]!; const beatmap = await getBeatmapById({ path: { id: score.beatmap_id, }, - }) + }); if (!beatmap || beatmap.error) { this.container.client.logger.error( `RecentScoreSubcommand: Couldn't fetch score's (id: ${score.id}) beatmap (id: ${score.beatmap_id}).`, - ) - throw new ExtendedError(`❓ I couldn't fetch score's beatmap data`) + ); + throw new ExtendedError(`❓ I couldn't fetch score's beatmap data`); } - const scoreEmbed = await embedPresets.getScoreEmbed(score, beatmap.data) + const scoreEmbed = await embedPresets.getScoreEmbed(score, beatmap.data); const buttons = new ActionRowBuilder().addComponents( new ButtonBuilder() .setURL(`https://${this.container.config.sunrise.uri}/score/${score.id}`) .setLabel("View score online") .setStyle(ButtonStyle.Link), - ) + ); await interaction.editReply({ embeds: [scoreEmbed], components: [buttons], - }) + }); } diff --git a/src/subcommands/osu/score.subcommand.ts b/src/subcommands/osu/score.subcommand.ts index 0397764..fd2cb3c 100644 --- a/src/subcommands/osu/score.subcommand.ts +++ b/src/subcommands/osu/score.subcommand.ts @@ -1,80 +1,82 @@ -import { getBeatmapById, getScoreById } from "../../lib/types/api" +import type { Subcommand } from "@sapphire/plugin-subcommands"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, - type SlashCommandSubcommandBuilder, -} from "discord.js" -import type { OsuCommand } from "../../commands/osu.command" -import type { Subcommand } from "@sapphire/plugin-subcommands" -import { ExtendedError } from "../../lib/extended-error" +} from "discord.js"; + +import type { OsuCommand } from "../../commands/osu.command"; +import { ExtendedError } from "../../lib/extended-error"; +import { getBeatmapById, getScoreById } from "../../lib/types/api"; export function addScoreSubcommand(command: SlashCommandSubcommandBuilder) { return command .setName("score") .setDescription("Get score data") - .addStringOption((o) => + .addStringOption(o => o.setName("score").setDescription("Score id or link").setRequired(false), - ) + ); } export async function chatInputRunScoreSubcommand( this: OsuCommand, interaction: Subcommand.ChatInputCommandInteraction, ) { - await interaction.deferReply() + await interaction.deferReply(); - const scoreStringOption = interaction.options.getString("score") - let scoreId = -1 + const scoreStringOption = interaction.options.getString("score"); + let scoreId = -1; if (scoreStringOption?.includes(this.container.config.sunrise.uri)) { - scoreId = Number(scoreStringOption.split("/").pop() ?? -1) - } else { - scoreId = Number(scoreStringOption ?? -1) + scoreId = Number(scoreStringOption.split("/").pop() ?? -1); + } + else { + scoreId = Number(scoreStringOption ?? -1); } - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; - if (scoreId == -1) { - throw new ExtendedError(`❓ Bad score id/link is provided`) + if (scoreId === -1) { + throw new ExtendedError(`❓ Bad score id/link is provided`); } const scoreResponse = await getScoreById({ path: { id: scoreId, }, - }) + }); if (scoreResponse.error) { - throw new ExtendedError(`❓ I couldn't find score with such data`) + throw new ExtendedError(`❓ I couldn't find score with such data`); } - const score = scoreResponse.data + const score = scoreResponse.data; const beatmap = await getBeatmapById({ path: { id: score.beatmap_id, }, - }) + }); if (!beatmap || beatmap.error) { this.container.client.logger.error( `ScoreSubcommand: Couldn't fetch score's (id: ${score.id}) beatmap (id: ${score.beatmap_id}).`, - ) - throw new ExtendedError(`❓ I couldn't fetch score's beatmap data`) + ); + throw new ExtendedError(`❓ I couldn't fetch score's beatmap data`); } - const scoreEmbed = await embedPresets.getScoreEmbed(score, beatmap.data) + const scoreEmbed = await embedPresets.getScoreEmbed(score, beatmap.data); const buttons = new ActionRowBuilder().addComponents( new ButtonBuilder() .setURL(`https://${this.container.config.sunrise.uri}/score/${score.id}`) .setLabel("View score online") .setStyle(ButtonStyle.Link), - ) + ); await interaction.editReply({ embeds: [scoreEmbed], components: [buttons], - }) + }); } diff --git a/src/subcommands/osu/scores.subcommand.ts b/src/subcommands/osu/scores.subcommand.ts index 6f03dbf..84f1ac2 100644 --- a/src/subcommands/osu/scores.subcommand.ts +++ b/src/subcommands/osu/scores.subcommand.ts @@ -1,110 +1,108 @@ +import type { Subcommand } from "@sapphire/plugin-subcommands"; +import type { HexColorString, SlashCommandSubcommandBuilder } from "discord.js"; +import { + bold, + EmbedBuilder, + hyperlink, + time, +} from "discord.js"; +import { getAverageColor } from "fast-average-color-node"; + +import type { OsuCommand } from "../../commands/osu.command"; +import { ExtendedError } from "../../lib/extended-error"; import { GameMode, getBeatmapById, getUserByIdScores, getUserSearch, ScoreTableType, -} from "../../lib/types/api" -import { - bold, - EmbedBuilder, - hyperlink, - time, - type HexColorString, - type SlashCommandSubcommandBuilder, -} from "discord.js" -import type { OsuCommand } from "../../commands/osu.command" -import type { Subcommand } from "@sapphire/plugin-subcommands" -import { getBeatmapStarRating } from "../../lib/utils/osu/star-rating.util" -import { getScoreRankEmoji } from "../../lib/utils/osu/emoji.util" -import { getAverageColor } from "fast-average-color-node" -import { ExtendedError } from "../../lib/extended-error" +} from "../../lib/types/api"; +import { getScoreRankEmoji } from "../../lib/utils/osu/emoji.util"; +import { getBeatmapStarRating } from "../../lib/utils/osu/star-rating.util"; export function addScoresSubcommand(command: SlashCommandSubcommandBuilder) { return command .setName("scores") .setDescription("Check user's scores") - .addStringOption((option) => + .addStringOption(option => option .setName("gamemode") .setDescription("Select gamemode") .setRequired(true) .setChoices( - Object.values(GameMode).map((mode) => ({ + Object.values(GameMode).map(mode => ({ name: mode.toString(), value: mode.toString(), })), ), ) - .addStringOption((option) => + .addStringOption(option => option .setName("type") .setDescription("Select scores type") .setRequired(true) .setChoices( - Object.values(ScoreTableType).map((mode) => ({ + Object.values(ScoreTableType).map(mode => ({ name: mode.toString(), value: mode.toString(), })), ), ) - .addStringOption((o) => o.setName("username").setDescription("User's username")) - .addUserOption((o) => + .addStringOption(o => o.setName("username").setDescription("User's username")) + .addUserOption(o => o.setName("discord").setDescription("Show users profile if he linked any"), ) - .addNumberOption((option) => option.setName("id").setDescription("User's id")) + .addNumberOption(option => option.setName("id").setDescription("User's id")); } export async function chatInputRunScoresSubcommand( this: OsuCommand, interaction: Subcommand.ChatInputCommandInteraction, ) { - await interaction.deferReply() + await interaction.deferReply(); - const userUsernameOption = interaction.options.getString("username") - const userDiscordOption = interaction.options.getUser("discord") + const userUsernameOption = interaction.options.getString("username"); + const userDiscordOption = interaction.options.getUser("discord"); - const gamemodeOption = interaction.options.getString("gamemode") as GameMode - const scoresTypeOption = interaction.options.getString("type") as ScoreTableType + const gamemodeOption = interaction.options.getString("gamemode") as GameMode; + const scoresTypeOption = interaction.options.getString("type") as ScoreTableType; - let userId: number | null = null - - const { embedPresets } = this.container.utilities + let userId: number | null = null; if (userUsernameOption) { const userSearchResponse = await getUserSearch({ query: { limit: 1, page: 1, query: userUsernameOption }, - }) + }); if (userSearchResponse.error || userSearchResponse.data.length <= 0) { throw new ExtendedError( - userSearchResponse?.error?.detail || - userSearchResponse?.error?.title || - "❓ I couldn't find user with such username", - ) + userSearchResponse?.error?.detail + || userSearchResponse?.error?.title + || "❓ I couldn't find user with such username", + ); } - userId = userSearchResponse.data[0]!.user_id + userId = userSearchResponse.data[0]!.user_id; } if (userId == null) { - const { db } = this.container + const { db } = this.container; const row = db.query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1").get({ $1: userDiscordOption ? userDiscordOption.id : interaction.user.id, - }) as null | { osu_user_id: number } + }) as null | { osu_user_id: number }; if (!row || !row.osu_user_id) { - throw new ExtendedError(`❓ Provided user didn't link their osu!sunrise account`) + throw new ExtendedError(`❓ Provided user didn't link their osu!sunrise account`); } - userId = row.osu_user_id + userId = row.osu_user_id; } - const { pagination } = this.container.utilities + const { pagination } = this.container.utilities; if (userId === null) { - throw new ExtendedError(`❓ Couldn't fetch requested user`) + throw new ExtendedError(`❓ Couldn't fetch requested user`); } const handlePagination = createHandleForScoresPagination.call( @@ -112,13 +110,13 @@ export async function chatInputRunScoresSubcommand( userId, gamemodeOption, scoresTypeOption, - ) + ); await pagination.createPaginationHandler(interaction, handlePagination, { pageSize: 10, currentPage: 1, totalPages: 0, - }) + }); } function createHandleForScoresPagination( @@ -127,14 +125,14 @@ function createHandleForScoresPagination( gamemode: GameMode, type: ScoreTableType, ) { - const { embedPresets } = this.container.utilities - const { config } = this.container - const missIcon = this.container.config.json.emojis.ranks.F + const { embedPresets } = this.container.utilities; + const { config } = this.container; + const missIcon = this.container.config.json.emojis.ranks.F; return async function handleUserScoresPagination(state: { - pageSize: number - totalPages: number - currentPage: number + pageSize: number; + totalPages: number; + currentPage: number; }) { const scoresResponse = await getUserByIdScores({ path: { @@ -142,62 +140,62 @@ function createHandleForScoresPagination( }, query: { mode: gamemode, - type: type, + type, page: state.currentPage, limit: state.pageSize, }, - }) + }); if (!scoresResponse || scoresResponse.error) { return embedPresets.getErrorEmbed( - scoresResponse?.error?.detail || - scoresResponse?.error?.title || - "Couldn't fetch requested user's scores!", - ) + scoresResponse?.error?.detail + || scoresResponse?.error?.title + || "Couldn't fetch requested user's scores!", + ); } - const uniqueBeatmapIds = [...new Set(scoresResponse.data.scores.flatMap((s) => s.beatmap_id))] + const uniqueBeatmapIds = [...new Set(scoresResponse.data.scores.flatMap(s => s.beatmap_id))]; const beatmapsResponses = await Promise.all( - uniqueBeatmapIds.map((id) => getBeatmapById({ path: { id } })), - ) + uniqueBeatmapIds.map(id => getBeatmapById({ path: { id } })), + ); - if (beatmapsResponses.some((b) => b.error)) { - return embedPresets.getErrorEmbed("Coudln't fetch all beatmaps for the scores!") + if (beatmapsResponses.some(b => b.error)) { + return embedPresets.getErrorEmbed("Coudln't fetch all beatmaps for the scores!"); } - const beatmaps = beatmapsResponses.map((r) => r.data) + const beatmaps = beatmapsResponses.map(r => r.data); if (scoresResponse.data.scores.length <= 0) { - return new EmbedBuilder().setDescription(`No scores to show...`) + return new EmbedBuilder().setDescription(`No scores to show...`); } const description = scoresResponse.data.scores.reduce((prev, curr, i) => { - const currentScorePlacement = i + (state.currentPage - 1) * state.pageSize + 1 - const whenPlayedDate = new Date(curr.when_played) + const currentScorePlacement = i + (state.currentPage - 1) * state.pageSize + 1; + const whenPlayedDate = new Date(curr.when_played); - const beatmap = beatmaps.find((b) => curr.beatmap_id === b?.id)! + const beatmap = beatmaps.find(b => curr.beatmap_id === b?.id)!; - const result = - `${bold("#" + currentScorePlacement)} ${hyperlink( + const result + = `${bold(`#${currentScorePlacement}`)} ${hyperlink( `${beatmap.artist} - ${beatmap?.title} [${beatmap?.version}]`, `https://${config.sunrise.uri}/beatmaps/${beatmap.id}`, - )} [★${getBeatmapStarRating(beatmap, curr.game_mode)}]\n` + - `${getScoreRankEmoji(curr.grade)} ${bold( + )} [★${getBeatmapStarRating(beatmap, curr.game_mode)}]\n` + + `${getScoreRankEmoji(curr.grade)} ${bold( beatmap.is_ranked ? curr.performance_points.toFixed(2) : "~ ", )}pp (${curr.accuracy.toFixed(2)}%) [${bold(`x${curr.max_combo}`)} / ${ beatmap.max_combo }] ${curr.count_miss}${missIcon} ${curr.mods ? bold(curr.mods) : ""} ${bold( time(whenPlayedDate, "R"), - )}` + )}`; - return prev + "\n" + result - }, "") + return `${prev}\n${result}`; + }, ""); - state.totalPages = Math.ceil(scoresResponse.data.total_count / state.pageSize) + state.totalPages = Math.ceil(scoresResponse.data.total_count / state.pageSize); - const { user } = scoresResponse.data.scores[0]! - const color = await getAverageColor(user.avatar_url) + const { user } = scoresResponse.data.scores[0]!; + const color = await getAverageColor(user.avatar_url); return new EmbedBuilder() .setTitle(`${type} Scores of ${user.username} in ${gamemode}`) @@ -207,6 +205,6 @@ function createHandleForScoresPagination( .setDescription(description) .setFooter({ text: `Page ${state.currentPage}/${state.totalPages} · submitted on osu!sunrise`, - }) - } + }); + }; } diff --git a/src/subcommands/osu/tests/link.subcommand.test.ts b/src/subcommands/osu/tests/link.subcommand.test.ts index cf6c17c..eb1ae2a 100644 --- a/src/subcommands/osu/tests/link.subcommand.test.ts +++ b/src/subcommands/osu/tests/link.subcommand.test.ts @@ -1,30 +1,31 @@ -import { expect, describe, it, beforeAll, afterAll, jest, mock, beforeEach } from "bun:test" -import { container } from "@sapphire/framework" -import { OsuCommand } from "../../../commands/osu.command" -import { Mocker } from "../../../lib/mock/mocker" -import { FakerGenerator } from "../../../lib/mock/faker.generator" -import { faker } from "@faker-js/faker" -import { ExtendedError } from "../../../lib/extended-error" +import { faker } from "@faker-js/faker"; +import { container } from "@sapphire/framework"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, jest, mock } from "bun:test"; + +import { OsuCommand } from "../../../commands/osu.command"; +import { ExtendedError } from "../../../lib/extended-error"; +import { FakerGenerator } from "../../../lib/mock/faker.generator"; +import { Mocker } from "../../../lib/mock/mocker"; describe("Osu Link Subcommand", () => { - let osuCommand: OsuCommand - let errorHandler: jest.Mock + let osuCommand: OsuCommand; + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - osuCommand = Mocker.createCommandInstance(OsuCommand) - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + osuCommand = Mocker.createCommandInstance(OsuCommand); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); it("should reply with success message when link is successful", async () => { - const editReplyMock = mock() - const username = faker.internet.username() + const editReplyMock = mock(); + const username = faker.internet.username(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -35,24 +36,24 @@ describe("Osu Link Subcommand", () => { }, }), "link", - ) + ); - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); Mocker.mockApiRequest("getUserSearch", async () => ({ - data: [{ user_id: osuUserId, username: username }], - })) + data: [{ user_id: osuUserId, username }], + })); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "link", - }) + }); const expectedEmbed = container.utilities.embedPresets.getSuccessEmbed( `🙂 You are now **${username}**!`, - ) + ); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); expect(editReplyMock).toHaveBeenLastCalledWith({ embeds: [ @@ -62,19 +63,19 @@ describe("Osu Link Subcommand", () => { }), }), ], - }) + }); - const { db } = container + const { db } = container; const row = db.query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1").get({ $1: interaction.user.id, - }) + }); - expect(row).toEqual({ osu_user_id: osuUserId.toString() }) - }) + expect(row).toEqual({ osu_user_id: osuUserId.toString() }); + }); it("should reply with error message when link fails", async () => { - const username = faker.internet.username() + const username = faker.internet.username(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -85,25 +86,25 @@ describe("Osu Link Subcommand", () => { }, }), "link", - ) + ); Mocker.mockApiRequest("getUserSearch", async () => ({ error: "User not found", - })) + })); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "link", - }) + }); - expect(errorHandler).toHaveBeenCalledWith(expect.any(ExtendedError), expect.anything()) + expect(errorHandler).toHaveBeenCalledWith(expect.any(ExtendedError), expect.anything()); - const { db } = container + const { db } = container; const row = db.query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1").get({ $1: interaction.user.id, - }) + }); - expect(row).toBeNull() - }) -}) + expect(row).toBeNull(); + }); +}); diff --git a/src/subcommands/osu/tests/profile.subcommand.test.ts b/src/subcommands/osu/tests/profile.subcommand.test.ts index 1cb3643..8deb101 100644 --- a/src/subcommands/osu/tests/profile.subcommand.test.ts +++ b/src/subcommands/osu/tests/profile.subcommand.test.ts @@ -1,33 +1,34 @@ -import { expect, describe, it, beforeAll, afterAll, beforeEach, jest, mock } from "bun:test" -import { container } from "@sapphire/framework" -import { OsuCommand } from "../../../commands/osu.command" -import { Mocker } from "../../../lib/mock/mocker" -import { FakerGenerator } from "../../../lib/mock/faker.generator" -import { faker } from "@faker-js/faker" -import { GameMode } from "../../../lib/types/api" +import { faker } from "@faker-js/faker"; +import { container } from "@sapphire/framework"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, jest, mock } from "bun:test"; + +import { OsuCommand } from "../../../commands/osu.command"; +import { FakerGenerator } from "../../../lib/mock/faker.generator"; +import { Mocker } from "../../../lib/mock/mocker"; +import { GameMode } from "../../../lib/types/api"; describe("Osu Profile Subcommand", () => { - let osuCommand: OsuCommand - let errorHandler: jest.Mock + let osuCommand: OsuCommand; + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - osuCommand = Mocker.createCommandInstance(OsuCommand) - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + osuCommand = Mocker.createCommandInstance(OsuCommand); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); it("should display profile when username is provided", async () => { - const editReplyMock = mock() - const username = faker.internet.username() + const editReplyMock = mock(); + const username = faker.internet.username(); const userWithStats = FakerGenerator.generateUserWithStats({ user: { username }, - }) + }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -40,11 +41,11 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userWithStats.user.user_id, username: username }], + data: [{ user_id: userWithStats.user.user_id, username }], }), getUserByIdByMode: async () => ({ data: userWithStats, @@ -55,19 +56,19 @@ describe("Osu Profile Subcommand", () => { getUserByIdGrades: async () => ({ data: { SS: 0, S: 0, A: 0, B: 0, C: 0, D: 0 }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const userEmbed = await osuCommand.container.utilities.embedPresets.getUserEmbed( userWithStats.user, userWithStats.stats, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -75,15 +76,15 @@ describe("Osu Profile Subcommand", () => { data: userEmbed.data, }), ], - }) - }) + }); + }); it("should display profile when user ID is provided", async () => { - const editReplyMock = mock() - const userId = faker.number.int({ min: 1, max: 1000000 }) + const editReplyMock = mock(); + const userId = faker.number.int({ min: 1, max: 1000000 }); const userWithStats = FakerGenerator.generateUserWithStats({ user: { user_id: userId }, - }) + }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -96,7 +97,7 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); Mocker.mockApiRequests({ getUserByIdByMode: async () => ({ @@ -108,19 +109,19 @@ describe("Osu Profile Subcommand", () => { getUserByIdGrades: async () => ({ data: { SS: 0, S: 0, A: 0, B: 0, C: 0, D: 0 }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const userEmbed = await osuCommand.container.utilities.embedPresets.getUserEmbed( userWithStats.user, userWithStats.stats, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -128,16 +129,16 @@ describe("Osu Profile Subcommand", () => { data: userEmbed.data, }), ], - }) - }) + }); + }); it("should display profile when Discord user is provided (linked account)", async () => { - const editReplyMock = mock() - const discordUser = FakerGenerator.generateUser() - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) + const editReplyMock = mock(); + const discordUser = FakerGenerator.generateUser(); + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); const userWithStats = FakerGenerator.generateUserWithStats({ user: { user_id: osuUserId }, - }) + }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -150,13 +151,13 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); - const { db } = container + const { db } = container; const insertUser = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertUser.run({ $1: discordUser.id, $2: osuUserId.toString() }) + ); + insertUser.run({ $1: discordUser.id, $2: osuUserId.toString() }); Mocker.mockApiRequests({ getUserByIdByMode: async () => ({ @@ -168,19 +169,19 @@ describe("Osu Profile Subcommand", () => { getUserByIdGrades: async () => ({ data: { SS: 0, S: 0, A: 0, B: 0, C: 0, D: 0 }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const userEmbed = await osuCommand.container.utilities.embedPresets.getUserEmbed( userWithStats.user, userWithStats.stats, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -188,16 +189,16 @@ describe("Osu Profile Subcommand", () => { data: userEmbed.data, }), ], - }) - }) + }); + }); it("should display profile for current user (no options, linked account)", async () => { - const editReplyMock = mock() - const currentUser = FakerGenerator.generateUser() - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) + const editReplyMock = mock(); + const currentUser = FakerGenerator.generateUser(); + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); const userWithStats = FakerGenerator.generateUserWithStats({ user: { user_id: osuUserId }, - }) + }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -211,13 +212,13 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); - const { db } = container + const { db } = container; const insertStmt = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertStmt.run({ $1: currentUser.id, $2: osuUserId.toString() }) + ); + insertStmt.run({ $1: currentUser.id, $2: osuUserId.toString() }); Mocker.mockApiRequests({ getUserByIdByMode: async () => ({ @@ -229,19 +230,19 @@ describe("Osu Profile Subcommand", () => { getUserByIdGrades: async () => ({ data: { SS: 0, S: 0, A: 0, B: 0, C: 0, D: 0 }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const userEmbed = await osuCommand.container.utilities.embedPresets.getUserEmbed( userWithStats.user, userWithStats.stats, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -249,17 +250,17 @@ describe("Osu Profile Subcommand", () => { data: userEmbed.data, }), ], - }) - }) + }); + }); it("should display profile with specific gamemode", async () => { - const editReplyMock = mock() - const userId = faker.number.int({ min: 1, max: 1000000 }) - const gamemode = GameMode.TAIKO + const editReplyMock = mock(); + const userId = faker.number.int({ min: 1, max: 1000000 }); + const gamemode = GameMode.TAIKO; const userWithStats = FakerGenerator.generateUserWithStats({ user: { user_id: userId }, - stats: { gamemode: gamemode }, - }) + stats: { gamemode }, + }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -272,7 +273,7 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); Mocker.mockApiRequests({ getUserByIdByMode: async () => ({ @@ -284,19 +285,19 @@ describe("Osu Profile Subcommand", () => { getUserByIdGrades: async () => ({ data: { SS: 0, S: 0, A: 0, B: 0, C: 0, D: 0 }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const userEmbed = await osuCommand.container.utilities.embedPresets.getUserEmbed( userWithStats.user, userWithStats.stats, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -304,11 +305,11 @@ describe("Osu Profile Subcommand", () => { data: userEmbed.data, }), ], - }) - }) + }); + }); it("should throw error when username is not found", async () => { - const username = faker.internet.username() + const username = faker.internet.username(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -321,29 +322,29 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ data: [], }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't find user with such username", }), expect.anything(), - ) - }) + ); + }); it("should throw error when user ID is not found", async () => { - const userId = faker.number.int({ min: 1, max: 1000000 }) + const userId = faker.number.int({ min: 1, max: 1000000 }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -356,29 +357,29 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); Mocker.mockApiRequests({ getUserByIdByMode: async () => ({ error: { error: "User not found" }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "Couldn't fetch requested user!", }), expect.anything(), - ) - }) + ); + }); it("should throw error when Discord user has no linked account", async () => { - const discordUser = FakerGenerator.generateUser() + const discordUser = FakerGenerator.generateUser(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -391,23 +392,23 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ Provided user didn't link their osu!sunrise account", }), expect.anything(), - ) - }) + ); + }); it("should throw error when current user has no linked account", async () => { - const currentUser = FakerGenerator.generateUser() + const currentUser = FakerGenerator.generateUser(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -421,23 +422,23 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ Provided user didn't link their osu!sunrise account", }), expect.anything(), - ) - }) + ); + }); it("should throw error when username search API fails", async () => { - const username = faker.internet.username() + const username = faker.internet.username(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -450,24 +451,24 @@ describe("Osu Profile Subcommand", () => { }, }), "profile", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ error: { error: "API Error" }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "profile", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't find user with such username", }), expect.anything(), - ) - }) -}) + ); + }); +}); diff --git a/src/subcommands/osu/tests/recent-score.subcommand.test.ts b/src/subcommands/osu/tests/recent-score.subcommand.test.ts index 71209b8..f7e4bad 100644 --- a/src/subcommands/osu/tests/recent-score.subcommand.test.ts +++ b/src/subcommands/osu/tests/recent-score.subcommand.test.ts @@ -1,35 +1,36 @@ -import { expect, describe, it, beforeAll, afterAll, beforeEach, jest, mock } from "bun:test" -import { container } from "@sapphire/framework" -import { OsuCommand } from "../../../commands/osu.command" -import { Mocker } from "../../../lib/mock/mocker" -import { FakerGenerator } from "../../../lib/mock/faker.generator" -import { faker } from "@faker-js/faker" -import { GameMode } from "../../../lib/types/api" -import { ButtonStyle } from "discord.js" +import { faker } from "@faker-js/faker"; +import { container } from "@sapphire/framework"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, jest, mock } from "bun:test"; +import { ButtonStyle } from "discord.js"; + +import { OsuCommand } from "../../../commands/osu.command"; +import { FakerGenerator } from "../../../lib/mock/faker.generator"; +import { Mocker } from "../../../lib/mock/mocker"; +import { GameMode } from "../../../lib/types/api"; describe("Osu Recent Score Subcommand", () => { - let osuCommand: OsuCommand - let errorHandler: jest.Mock + let osuCommand: OsuCommand; + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - osuCommand = Mocker.createCommandInstance(OsuCommand) - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + osuCommand = Mocker.createCommandInstance(OsuCommand); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); it("should display recent score when username is provided", async () => { - const editReplyMock = mock() - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) + const editReplyMock = mock(); + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); - const mockScore = FakerGenerator.generateScore() - const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }) + const mockScore = FakerGenerator.generateScore(); + const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -41,11 +42,11 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), getUserByIdScores: async () => ({ data: { scores: [mockScore], total_count: 1 }, @@ -53,19 +54,19 @@ describe("Osu Recent Score Subcommand", () => { getBeatmapById: async () => ({ data: mockBeatmap, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const scoreEmbed = await osuCommand.container.utilities.embedPresets.getScoreEmbed( mockScore, mockBeatmap, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -85,16 +86,16 @@ describe("Osu Recent Score Subcommand", () => { ], }), ], - }) - }) + }); + }); it("should display recent score when Discord user is provided (linked account)", async () => { - const editReplyMock = mock() - const discordUser = FakerGenerator.generateUser() - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) + const editReplyMock = mock(); + const discordUser = FakerGenerator.generateUser(); + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); - const mockScore = FakerGenerator.generateScore() - const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }) + const mockScore = FakerGenerator.generateScore(); + const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -106,13 +107,13 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); - const { db } = container + const { db } = container; const insertUser = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertUser.run({ $1: discordUser.id, $2: osuUserId.toString() }) + ); + insertUser.run({ $1: discordUser.id, $2: osuUserId.toString() }); Mocker.mockApiRequests({ getUserByIdScores: async () => ({ @@ -121,19 +122,19 @@ describe("Osu Recent Score Subcommand", () => { getBeatmapById: async () => ({ data: mockBeatmap, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const scoreEmbed = await osuCommand.container.utilities.embedPresets.getScoreEmbed( mockScore, mockBeatmap, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -153,16 +154,16 @@ describe("Osu Recent Score Subcommand", () => { ], }), ], - }) - }) + }); + }); it("should display recent score for current user (no options, linked account)", async () => { - const editReplyMock = mock() - const currentUser = FakerGenerator.generateUser() - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) + const editReplyMock = mock(); + const currentUser = FakerGenerator.generateUser(); + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); - const mockScore = FakerGenerator.generateScore() - const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }) + const mockScore = FakerGenerator.generateScore(); + const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -175,13 +176,13 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); - const { db } = container + const { db } = container; const insertUser = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertUser.run({ $1: currentUser.id, $2: osuUserId.toString() }) + ); + insertUser.run({ $1: currentUser.id, $2: osuUserId.toString() }); Mocker.mockApiRequests({ getUserByIdScores: async () => ({ @@ -190,19 +191,19 @@ describe("Osu Recent Score Subcommand", () => { getBeatmapById: async () => ({ data: mockBeatmap, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const scoreEmbed = await osuCommand.container.utilities.embedPresets.getScoreEmbed( mockScore, mockBeatmap, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -222,17 +223,17 @@ describe("Osu Recent Score Subcommand", () => { ], }), ], - }) - }) + }); + }); it("should display recent score with specific gamemode", async () => { - const editReplyMock = mock() - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) - const gamemode = GameMode.TAIKO + const editReplyMock = mock(); + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); + const gamemode = GameMode.TAIKO; - const mockScore = FakerGenerator.generateScore({ game_mode: gamemode }) - const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }) + const mockScore = FakerGenerator.generateScore({ game_mode: gamemode }); + const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -240,19 +241,21 @@ describe("Osu Recent Score Subcommand", () => { editReply: editReplyMock, options: { getString: jest.fn((name: string) => { - if (name === "username") return username - if (name === "gamemode") return gamemode - return null + if (name === "username") + return username; + if (name === "gamemode") + return gamemode; + return null; }), getUser: jest.fn().mockReturnValue(null), }, }), "rs", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), getUserByIdScores: async () => ({ data: { scores: [mockScore], total_count: 1 }, @@ -260,19 +263,19 @@ describe("Osu Recent Score Subcommand", () => { getBeatmapById: async () => ({ data: mockBeatmap, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const scoreEmbed = await osuCommand.container.utilities.embedPresets.getScoreEmbed( mockScore, mockBeatmap, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -281,11 +284,11 @@ describe("Osu Recent Score Subcommand", () => { }), ], components: expect.anything(), - }) - }) + }); + }); it("should throw error when username is not found", async () => { - const username = faker.internet.username() + const username = faker.internet.username(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -297,29 +300,29 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ data: [], }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't find user with such username", }), expect.anything(), - ) - }) + ); + }); it("should throw error when Discord user has no linked account", async () => { - const discordUser = FakerGenerator.generateUser() + const discordUser = FakerGenerator.generateUser(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -331,23 +334,23 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ Provided user didn't link their osu!sunrise account", }), expect.anything(), - ) - }) + ); + }); it("should throw error when current user has no linked account", async () => { - const currentUser = FakerGenerator.generateUser() + const currentUser = FakerGenerator.generateUser(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -360,24 +363,24 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ Provided user didn't link their osu!sunrise account", }), expect.anything(), - ) - }) + ); + }); it("should throw error when user has no recent scores", async () => { - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -389,35 +392,35 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), getUserByIdScores: async () => ({ data: { scores: [], total_count: 0 }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "This user has no recent scores", }), expect.anything(), - ) - }) + ); + }); it("should throw error when beatmap is not found", async () => { - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); - const mockScore = FakerGenerator.generateScore() + const mockScore = FakerGenerator.generateScore(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -429,11 +432,11 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), getUserByIdScores: async () => ({ data: { scores: [mockScore], total_count: 1 }, @@ -441,23 +444,23 @@ describe("Osu Recent Score Subcommand", () => { getBeatmapById: async () => ({ error: "Beatmap not found", }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't fetch score's beatmap data", }), expect.anything(), - ) - }) + ); + }); it("should throw error when username search API fails", async () => { - const username = faker.internet.username() + const username = faker.internet.username(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -469,30 +472,30 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ error: { error: "API Error" }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't find user with such username", }), expect.anything(), - ) - }) + ); + }); it("should throw error when recent scores API fails", async () => { - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -504,27 +507,27 @@ describe("Osu Recent Score Subcommand", () => { }, }), "rs", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), getUserByIdScores: async () => ({ error: { error: "Scores API Error" }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "rs", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "Couldn't fetch requested user's recent score!", }), expect.anything(), - ) - }) -}) + ); + }); +}); diff --git a/src/subcommands/osu/tests/score.subcommand.test.ts b/src/subcommands/osu/tests/score.subcommand.test.ts index 6c5cf7c..482537d 100644 --- a/src/subcommands/osu/tests/score.subcommand.test.ts +++ b/src/subcommands/osu/tests/score.subcommand.test.ts @@ -1,33 +1,34 @@ -import { expect, describe, it, beforeAll, afterAll, beforeEach, jest, mock } from "bun:test" -import { container } from "@sapphire/framework" -import { OsuCommand } from "../../../commands/osu.command" -import { Mocker } from "../../../lib/mock/mocker" -import { FakerGenerator } from "../../../lib/mock/faker.generator" -import { faker } from "@faker-js/faker" -import { ButtonStyle } from "discord.js" +import { faker } from "@faker-js/faker"; +import { container } from "@sapphire/framework"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, jest, mock } from "bun:test"; +import { ButtonStyle } from "discord.js"; + +import { OsuCommand } from "../../../commands/osu.command"; +import { FakerGenerator } from "../../../lib/mock/faker.generator"; +import { Mocker } from "../../../lib/mock/mocker"; describe("Osu Score Subcommand", () => { - let osuCommand: OsuCommand - let errorHandler: jest.Mock + let osuCommand: OsuCommand; + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - osuCommand = Mocker.createCommandInstance(OsuCommand) - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + osuCommand = Mocker.createCommandInstance(OsuCommand); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); it("should display score embed when score ID is provided", async () => { - const editReplyMock = mock() - const scoreId = faker.number.int({ min: 1, max: 1000000 }) + const editReplyMock = mock(); + const scoreId = faker.number.int({ min: 1, max: 1000000 }); - const mockScore = FakerGenerator.generateScore({ id: scoreId }) - const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }) + const mockScore = FakerGenerator.generateScore({ id: scoreId }); + const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -38,7 +39,7 @@ describe("Osu Score Subcommand", () => { }, }), "score", - ) + ); Mocker.mockApiRequests({ getScoreById: async () => ({ @@ -47,19 +48,19 @@ describe("Osu Score Subcommand", () => { getBeatmapById: async () => ({ data: mockBeatmap, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "score", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const scoreEmbed = await osuCommand.container.utilities.embedPresets.getScoreEmbed( mockScore, mockBeatmap, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -79,17 +80,17 @@ describe("Osu Score Subcommand", () => { ], }), ], - }) - }) + }); + }); it("should handle score link with sunrise URI", async () => { - const editReplyMock = mock() - const scoreId = faker.number.int({ min: 1, max: 1000000 }) - const sunriseUri = container.config.sunrise.uri - const scoreLink = `https://${sunriseUri}/score/${scoreId}` + const editReplyMock = mock(); + const scoreId = faker.number.int({ min: 1, max: 1000000 }); + const sunriseUri = container.config.sunrise.uri; + const scoreLink = `https://${sunriseUri}/score/${scoreId}`; - const mockScore = FakerGenerator.generateScore({ id: scoreId }) - const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }) + const mockScore = FakerGenerator.generateScore({ id: scoreId }); + const mockBeatmap = FakerGenerator.generateBeatmap({ id: mockScore.beatmap_id }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -100,7 +101,7 @@ describe("Osu Score Subcommand", () => { }, }), "score", - ) + ); Mocker.mockApiRequests({ getScoreById: async () => ({ @@ -109,19 +110,19 @@ describe("Osu Score Subcommand", () => { getBeatmapById: async () => ({ data: mockBeatmap, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "score", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const scoreEmbed = await osuCommand.container.utilities.embedPresets.getScoreEmbed( mockScore, mockBeatmap, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -142,8 +143,8 @@ describe("Osu Score Subcommand", () => { ], }), ], - }) - }) + }); + }); it("should throw error when invalid score ID is provided", async () => { const interaction = FakerGenerator.withSubcommand( @@ -155,7 +156,7 @@ describe("Osu Score Subcommand", () => { }, }), "score", - ) + ); Mocker.mockApiRequests({ getScoreById: async () => ({ @@ -164,20 +165,20 @@ describe("Osu Score Subcommand", () => { getBeatmapById: async () => ({ error: "Beatmap not found", }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "score", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't find score with such data", }), expect.anything(), - ) - }) + ); + }); it("should throw error when no score ID is provided", async () => { const interaction = FakerGenerator.withSubcommand( @@ -189,23 +190,23 @@ describe("Osu Score Subcommand", () => { }, }), "score", - ) + ); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "score", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ Bad score id/link is provided", }), expect.anything(), - ) - }) + ); + }); it("should throw error when score is not found", async () => { - const scoreId = faker.number.int({ min: 1, max: 1000000 }) + const scoreId = faker.number.int({ min: 1, max: 1000000 }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -216,29 +217,29 @@ describe("Osu Score Subcommand", () => { }, }), "score", - ) + ); Mocker.mockApiRequest("getScoreById", async () => ({ error: "Score not found", - })) + })); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "score", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't find score with such data", }), expect.anything(), - ) - }) + ); + }); it("should throw error when beatmap is not found", async () => { - const scoreId = faker.number.int({ min: 1, max: 1000000 }) + const scoreId = faker.number.int({ min: 1, max: 1000000 }); - const mockScore = FakerGenerator.generateScore({ id: scoreId }) + const mockScore = FakerGenerator.generateScore({ id: scoreId }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -249,7 +250,7 @@ describe("Osu Score Subcommand", () => { }, }), "score", - ) + ); Mocker.mockApiRequests({ getScoreById: async () => ({ @@ -258,18 +259,18 @@ describe("Osu Score Subcommand", () => { getBeatmapById: async () => ({ error: "Beatmap not found", }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "score", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't fetch score's beatmap data", }), expect.anything(), - ) - }) -}) + ); + }); +}); diff --git a/src/subcommands/osu/tests/scores.subcommand.test.ts b/src/subcommands/osu/tests/scores.subcommand.test.ts index 2aca236..9445ef0 100644 --- a/src/subcommands/osu/tests/scores.subcommand.test.ts +++ b/src/subcommands/osu/tests/scores.subcommand.test.ts @@ -1,35 +1,36 @@ -import { expect, describe, it, beforeAll, afterAll, jest, mock, beforeEach } from "bun:test" -import { container } from "@sapphire/framework" -import { OsuCommand } from "../../../commands/osu.command" -import { Mocker } from "../../../lib/mock/mocker" -import { FakerGenerator } from "../../../lib/mock/faker.generator" -import { faker } from "@faker-js/faker" -import { GameMode, ScoreTableType } from "../../../lib/types/api" +import { faker } from "@faker-js/faker"; +import { container } from "@sapphire/framework"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, jest, mock } from "bun:test"; + +import { OsuCommand } from "../../../commands/osu.command"; +import { FakerGenerator } from "../../../lib/mock/faker.generator"; +import { Mocker } from "../../../lib/mock/mocker"; +import { GameMode, ScoreTableType } from "../../../lib/types/api"; describe("Osu Scores Subcommand", () => { - let osuCommand: OsuCommand - let errorHandler: jest.Mock + let osuCommand: OsuCommand; + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - osuCommand = Mocker.createCommandInstance(OsuCommand) - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + osuCommand = Mocker.createCommandInstance(OsuCommand); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); it("should create pagination handler when username is provided", async () => { - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) - const gamemode = GameMode.STANDARD - const scoreType = ScoreTableType.TOP + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); + const gamemode = GameMode.STANDARD; + const scoreType = ScoreTableType.TOP; - const paginationCreateMock = mock() - container.utilities.pagination.createPaginationHandler = paginationCreateMock + const paginationCreateMock = mock(); + container.utilities.pagination.createPaginationHandler = paginationCreateMock; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -37,30 +38,33 @@ describe("Osu Scores Subcommand", () => { editReply: mock(), options: { getString: jest.fn((name: string) => { - if (name === "username") return username - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "username") + return username; + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(null), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); expect(paginationCreateMock).toHaveBeenCalledWith( interaction, expect.any(Function), @@ -69,17 +73,17 @@ describe("Osu Scores Subcommand", () => { currentPage: 1, totalPages: 0, }), - ) - }) + ); + }); it("should create pagination handler when Discord user is provided (linked account)", async () => { - const discordUser = FakerGenerator.generateUser() - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) - const gamemode = GameMode.TAIKO - const scoreType = ScoreTableType.RECENT + const discordUser = FakerGenerator.generateUser(); + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); + const gamemode = GameMode.TAIKO; + const scoreType = ScoreTableType.RECENT; - const paginationCreateMock = mock() - container.utilities.pagination.createPaginationHandler = paginationCreateMock + const paginationCreateMock = mock(); + container.utilities.pagination.createPaginationHandler = paginationCreateMock; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -87,29 +91,31 @@ describe("Osu Scores Subcommand", () => { editReply: mock(), options: { getString: jest.fn((name: string) => { - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(discordUser), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); - const { db } = container + const { db } = container; const insertUser = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertUser.run({ $1: discordUser.id, $2: osuUserId.toString() }) + ); + insertUser.run({ $1: discordUser.id, $2: osuUserId.toString() }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); expect(paginationCreateMock).toHaveBeenCalledWith( interaction, expect.any(Function), @@ -118,17 +124,17 @@ describe("Osu Scores Subcommand", () => { currentPage: 1, totalPages: 0, }), - ) - }) + ); + }); it("should create pagination handler for current user (no options, linked account)", async () => { - const currentUser = FakerGenerator.generateUser() - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) - const gamemode = GameMode.MANIA - const scoreType = ScoreTableType.BEST + const currentUser = FakerGenerator.generateUser(); + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); + const gamemode = GameMode.MANIA; + const scoreType = ScoreTableType.BEST; - const paginationCreateMock = mock() - container.utilities.pagination.createPaginationHandler = paginationCreateMock + const paginationCreateMock = mock(); + container.utilities.pagination.createPaginationHandler = paginationCreateMock; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -137,29 +143,31 @@ describe("Osu Scores Subcommand", () => { user: currentUser, options: { getString: jest.fn((name: string) => { - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(null), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); - const { db } = container + const { db } = container; const insertUser = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertUser.run({ $1: currentUser.id, $2: osuUserId.toString() }) + ); + insertUser.run({ $1: currentUser.id, $2: osuUserId.toString() }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); expect(paginationCreateMock).toHaveBeenCalledWith( interaction, expect.any(Function), @@ -168,13 +176,13 @@ describe("Osu Scores Subcommand", () => { currentPage: 1, totalPages: 0, }), - ) - }) + ); + }); it("should throw error when username is not found", async () => { - const username = faker.internet.username() - const gamemode = GameMode.STANDARD - const scoreType = ScoreTableType.TOP + const username = faker.internet.username(); + const gamemode = GameMode.STANDARD; + const scoreType = ScoreTableType.TOP; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -182,41 +190,44 @@ describe("Osu Scores Subcommand", () => { editReply: mock(), options: { getString: jest.fn((name: string) => { - if (name === "username") return username - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "username") + return username; + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(null), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ data: [], }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't find user with such username", }), expect.anything(), - ) - }) + ); + }); it("should throw error when Discord user has no linked account", async () => { - const discordUser = FakerGenerator.generateUser() - const gamemode = GameMode.STANDARD - const scoreType = ScoreTableType.TOP + const discordUser = FakerGenerator.generateUser(); + const gamemode = GameMode.STANDARD; + const scoreType = ScoreTableType.TOP; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -224,34 +235,36 @@ describe("Osu Scores Subcommand", () => { editReply: mock(), options: { getString: jest.fn((name: string) => { - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(discordUser), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ Provided user didn't link their osu!sunrise account", }), expect.anything(), - ) - }) + ); + }); it("should throw error when current user has no linked account", async () => { - const currentUser = FakerGenerator.generateUser() - const gamemode = GameMode.STANDARD - const scoreType = ScoreTableType.TOP + const currentUser = FakerGenerator.generateUser(); + const gamemode = GameMode.STANDARD; + const scoreType = ScoreTableType.TOP; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -260,34 +273,36 @@ describe("Osu Scores Subcommand", () => { user: currentUser, options: { getString: jest.fn((name: string) => { - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(null), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ Provided user didn't link their osu!sunrise account", }), expect.anything(), - ) - }) + ); + }); it("should throw error when username search API fails", async () => { - const username = faker.internet.username() - const gamemode = GameMode.STANDARD - const scoreType = ScoreTableType.TOP + const username = faker.internet.username(); + const gamemode = GameMode.STANDARD; + const scoreType = ScoreTableType.TOP; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -295,49 +310,52 @@ describe("Osu Scores Subcommand", () => { editReply: mock(), options: { getString: jest.fn((name: string) => { - if (name === "username") return username - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "username") + return username; + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(null), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ error: { error: "API Error" }, }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ I couldn't find user with such username", }), expect.anything(), - ) - }) + ); + }); it("should handle pagination callback correctly with scores", async () => { - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) - const gamemode = GameMode.STANDARD - const scoreType = ScoreTableType.TOP - - let capturedPaginationHandler: any = null - const paginationCreateMock = mock((interaction: any, handler: any, state: any) => { - capturedPaginationHandler = handler - return Promise.resolve() - }) - container.utilities.pagination.createPaginationHandler = paginationCreateMock + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); + const gamemode = GameMode.STANDARD; + const scoreType = ScoreTableType.TOP; + + let capturedPaginationHandler: any = null; + const paginationCreateMock = mock((interaction: any, handler: any, _state: any) => { + capturedPaginationHandler = handler; + return Promise.resolve(); + }); + container.utilities.pagination.createPaginationHandler = paginationCreateMock; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -345,72 +363,77 @@ describe("Osu Scores Subcommand", () => { editReply: mock(), options: { getString: jest.fn((name: string) => { - if (name === "username") return username - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "username") + return username; + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(null), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); - expect(capturedPaginationHandler).not.toBeNull() + expect(capturedPaginationHandler).not.toBeNull(); - const mockScore1 = FakerGenerator.generateScore() - const mockScore2 = FakerGenerator.generateScore() - const mockBeatmap1 = FakerGenerator.generateBeatmap({ id: mockScore1.beatmap_id }) - const mockBeatmap2 = FakerGenerator.generateBeatmap({ id: mockScore2.beatmap_id }) + const mockScore1 = FakerGenerator.generateScore(); + const mockScore2 = FakerGenerator.generateScore(); + const mockBeatmap1 = FakerGenerator.generateBeatmap({ id: mockScore1.beatmap_id }); + const mockBeatmap2 = FakerGenerator.generateBeatmap({ id: mockScore2.beatmap_id }); Mocker.mockApiRequests({ getUserByIdScores: async () => ({ data: { scores: [mockScore1, mockScore2], total_count: 20 }, }), getBeatmapById: async ({ path }: { path: { id: number } }) => { - if (path.id === mockScore1.beatmap_id) return { data: mockBeatmap1 } - if (path.id === mockScore2.beatmap_id) return { data: mockBeatmap2 } - return { error: "Not found" } + if (path.id === mockScore1.beatmap_id) + return { data: mockBeatmap1 }; + if (path.id === mockScore2.beatmap_id) + return { data: mockBeatmap2 }; + return { error: "Not found" }; }, - }) + }); const result = await capturedPaginationHandler({ pageSize: 10, currentPage: 1, totalPages: 0, - }) + }); - expect(result).toBeDefined() - expect(result.data).toBeDefined() - expect(result.data.title).toContain(gamemode) - expect(result.data.title).toContain(scoreType) - expect(result.data.description).toBeDefined() - }) + expect(result).toBeDefined(); + expect(result.data).toBeDefined(); + expect(result.data.title).toContain(gamemode); + expect(result.data.title).toContain(scoreType); + expect(result.data.description).toBeDefined(); + }); it("should handle pagination callback with no scores", async () => { - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) - const gamemode = GameMode.STANDARD - const scoreType = ScoreTableType.TOP - - let capturedPaginationHandler: any = null - const paginationCreateMock = mock((interaction: any, handler: any, state: any) => { - capturedPaginationHandler = handler - return Promise.resolve() - }) - container.utilities.pagination.createPaginationHandler = paginationCreateMock + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); + const gamemode = GameMode.STANDARD; + const scoreType = ScoreTableType.TOP; + + let capturedPaginationHandler: any = null; + const paginationCreateMock = mock((interaction: any, handler: any, _state: any) => { + capturedPaginationHandler = handler; + return Promise.resolve(); + }); + container.utilities.pagination.createPaginationHandler = paginationCreateMock; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -418,57 +441,60 @@ describe("Osu Scores Subcommand", () => { editReply: mock(), options: { getString: jest.fn((name: string) => { - if (name === "username") return username - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "username") + return username; + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(null), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); Mocker.mockApiRequests({ getUserByIdScores: async () => ({ data: { scores: [], total_count: 0 }, }), - }) + }); const result = await capturedPaginationHandler({ pageSize: 10, currentPage: 1, totalPages: 0, - }) + }); - expect(result).toBeDefined() - expect(result.data.description).toContain("No scores to show") - }) + expect(result).toBeDefined(); + expect(result.data.description).toContain("No scores to show"); + }); it("should handle pagination callback with API error", async () => { - const username = faker.internet.username() - const userId = faker.number.int({ min: 1, max: 1000000 }) - const gamemode = GameMode.STANDARD - const scoreType = ScoreTableType.TOP - - let capturedPaginationHandler: any = null - const paginationCreateMock = mock((interaction: any, handler: any, state: any) => { - capturedPaginationHandler = handler - return Promise.resolve() - }) - container.utilities.pagination.createPaginationHandler = paginationCreateMock + const username = faker.internet.username(); + const userId = faker.number.int({ min: 1, max: 1000000 }); + const gamemode = GameMode.STANDARD; + const scoreType = ScoreTableType.TOP; + + let capturedPaginationHandler: any = null; + const paginationCreateMock = mock((interaction: any, handler: any, _state: any) => { + capturedPaginationHandler = handler; + return Promise.resolve(); + }); + container.utilities.pagination.createPaginationHandler = paginationCreateMock; const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -476,42 +502,45 @@ describe("Osu Scores Subcommand", () => { editReply: mock(), options: { getString: jest.fn((name: string) => { - if (name === "username") return username - if (name === "gamemode") return gamemode - if (name === "type") return scoreType - return null + if (name === "username") + return username; + if (name === "gamemode") + return gamemode; + if (name === "type") + return scoreType; + return null; }), getUser: jest.fn().mockReturnValue(null), getNumber: jest.fn().mockReturnValue(null), }, }), "scores", - ) + ); Mocker.mockApiRequests({ getUserSearch: async () => ({ - data: [{ user_id: userId, username: username }], + data: [{ user_id: userId, username }], }), - }) + }); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "scores", - }) + }); Mocker.mockApiRequests({ getUserByIdScores: async () => ({ error: { error: "Scores API Error" }, }), - }) + }); const result = await capturedPaginationHandler({ pageSize: 10, currentPage: 1, totalPages: 0, - }) + }); - expect(result).toBeDefined() - expect(result.data).toBeDefined() - }) -}) + expect(result).toBeDefined(); + expect(result.data).toBeDefined(); + }); +}); diff --git a/src/subcommands/osu/tests/unlink.subcommand.test.ts b/src/subcommands/osu/tests/unlink.subcommand.test.ts index 2c35760..e3f8239 100644 --- a/src/subcommands/osu/tests/unlink.subcommand.test.ts +++ b/src/subcommands/osu/tests/unlink.subcommand.test.ts @@ -1,30 +1,32 @@ -import { expect, describe, it, beforeAll, afterAll, beforeEach, jest, mock } from "bun:test" -import { container } from "@sapphire/framework" -import { OsuCommand } from "../../../commands/osu.command" -import { Mocker } from "../../../lib/mock/mocker" -import { FakerGenerator } from "../../../lib/mock/faker.generator" -import { faker } from "@faker-js/faker" +import { faker } from "@faker-js/faker"; +import { container } from "@sapphire/framework"; +import type { jest } from "bun:test"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, mock } from "bun:test"; + +import { OsuCommand } from "../../../commands/osu.command"; +import { FakerGenerator } from "../../../lib/mock/faker.generator"; +import { Mocker } from "../../../lib/mock/mocker"; describe("Osu Unlink Subcommand", () => { - let osuCommand: OsuCommand - let errorHandler: jest.Mock + let osuCommand: OsuCommand; + let errorHandler: jest.Mock; beforeAll(() => { - Mocker.createSapphireClientInstance() - osuCommand = Mocker.createCommandInstance(OsuCommand) - errorHandler = Mocker.createErrorHandler() - }) + Mocker.createSapphireClientInstance(); + osuCommand = Mocker.createCommandInstance(OsuCommand); + errorHandler = Mocker.createErrorHandler(); + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => Mocker.beforeEachCleanup(errorHandler)) + beforeEach(() => Mocker.beforeEachCleanup(errorHandler)); it("should successfully unlink account", async () => { - const editReplyMock = mock() - const currentUser = FakerGenerator.generateUser() - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) + const editReplyMock = mock(); + const currentUser = FakerGenerator.generateUser(); + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -34,30 +36,30 @@ describe("Osu Unlink Subcommand", () => { options: {}, }), "unlink", - ) + ); - const { db } = container + const { db } = container; const insertUser = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertUser.run({ $1: currentUser.id, $2: osuUserId.toString() }) + ); + insertUser.run({ $1: currentUser.id, $2: osuUserId.toString() }); const beforeRow = db .query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1") - .get({ $1: currentUser.id }) as { osu_user_id: string } | null - expect(beforeRow).not.toBeNull() - expect(beforeRow?.osu_user_id).toBe(osuUserId.toString()) + .get({ $1: currentUser.id }) as { osu_user_id: string } | null; + expect(beforeRow).not.toBeNull(); + expect(beforeRow?.osu_user_id).toBe(osuUserId.toString()); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "unlink", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const expectedEmbed = container.utilities.embedPresets.getSuccessEmbed( `I successfully unlinked your account!`, - ) + ); expect(editReplyMock).toHaveBeenCalledWith({ embeds: [ @@ -67,17 +69,17 @@ describe("Osu Unlink Subcommand", () => { }), }), ], - }) + }); const afterRow = db .query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1") - .get({ $1: currentUser.id }) + .get({ $1: currentUser.id }); - expect(afterRow).toBeNull() - }) + expect(afterRow).toBeNull(); + }); it("should throw error when user has no linked account", async () => { - const currentUser = FakerGenerator.generateUser() + const currentUser = FakerGenerator.generateUser(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -87,50 +89,50 @@ describe("Osu Unlink Subcommand", () => { options: {}, }), "unlink", - ) + ); - const { db } = container + const { db } = container; const row = db.query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1").get({ $1: currentUser.id, - }) - expect(row).toBeNull() + }); + expect(row).toBeNull(); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "unlink", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ You don't have any linked account", }), expect.anything(), - ) - }) + ); + }); it("should only unlink the current user's account", async () => { - const user1 = FakerGenerator.generateUser() - const user2 = FakerGenerator.generateUser() - const osuUserId1 = faker.number.int({ min: 1, max: 1000000 }) - const osuUserId2 = faker.number.int({ min: 1, max: 1000000 }) + const user1 = FakerGenerator.generateUser(); + const user2 = FakerGenerator.generateUser(); + const osuUserId1 = faker.number.int({ min: 1, max: 1000000 }); + const osuUserId2 = faker.number.int({ min: 1, max: 1000000 }); - const { db } = container + const { db } = container; const insertUser = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertUser.run({ $1: user1.id, $2: osuUserId1.toString() }) - insertUser.run({ $1: user2.id, $2: osuUserId2.toString() }) + ); + insertUser.run({ $1: user1.id, $2: osuUserId1.toString() }); + insertUser.run({ $1: user2.id, $2: osuUserId2.toString() }); const beforeRow1 = db .query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1") - .get({ $1: user1.id }) as { osu_user_id: string } | null + .get({ $1: user1.id }) as { osu_user_id: string } | null; const beforeRow2 = db .query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1") - .get({ $1: user2.id }) as { osu_user_id: string } | null + .get({ $1: user2.id }) as { osu_user_id: string } | null; - expect(beforeRow1).not.toBeNull() - expect(beforeRow2).not.toBeNull() + expect(beforeRow1).not.toBeNull(); + expect(beforeRow2).not.toBeNull(); const interaction = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -140,37 +142,37 @@ describe("Osu Unlink Subcommand", () => { options: {}, }), "unlink", - ) + ); await osuCommand.chatInputRun(interaction, { commandId: faker.string.uuid(), commandName: "unlink", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const afterRow1 = db .query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1") - .get({ $1: user1.id }) - expect(afterRow1).toBeNull() + .get({ $1: user1.id }); + expect(afterRow1).toBeNull(); const afterRow2 = db .query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1") - .get({ $1: user2.id }) as { osu_user_id: string } | null + .get({ $1: user2.id }) as { osu_user_id: string } | null; - expect(afterRow2).not.toBeNull() - expect(afterRow2?.osu_user_id).toBe(osuUserId2.toString()) - }) + expect(afterRow2).not.toBeNull(); + expect(afterRow2?.osu_user_id).toBe(osuUserId2.toString()); + }); it("should handle multiple unlink attempts gracefully", async () => { - const currentUser = FakerGenerator.generateUser() - const osuUserId = faker.number.int({ min: 1, max: 1000000 }) + const currentUser = FakerGenerator.generateUser(); + const osuUserId = faker.number.int({ min: 1, max: 1000000 }); - const { db } = container + const { db } = container; const insertUser = db.prepare( "INSERT INTO connections (discord_user_id, osu_user_id) VALUES ($1, $2)", - ) - insertUser.run({ $1: currentUser.id, $2: osuUserId.toString() }) + ); + insertUser.run({ $1: currentUser.id, $2: osuUserId.toString() }); const interaction1 = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -180,19 +182,19 @@ describe("Osu Unlink Subcommand", () => { options: {}, }), "unlink", - ) + ); await osuCommand.chatInputRun(interaction1, { commandId: faker.string.uuid(), commandName: "unlink", - }) + }); - expect(errorHandler).not.toBeCalled() + expect(errorHandler).not.toBeCalled(); const afterFirstUnlink = db .query("SELECT osu_user_id FROM connections WHERE discord_user_id = $1") - .get({ $1: currentUser.id }) - expect(afterFirstUnlink).toBeNull() + .get({ $1: currentUser.id }); + expect(afterFirstUnlink).toBeNull(); const interaction2 = FakerGenerator.withSubcommand( FakerGenerator.generateInteraction({ @@ -202,18 +204,18 @@ describe("Osu Unlink Subcommand", () => { options: {}, }), "unlink", - ) + ); await osuCommand.chatInputRun(interaction2, { commandId: faker.string.uuid(), commandName: "unlink", - }) + }); expect(errorHandler).toHaveBeenCalledWith( expect.objectContaining({ message: "❓ You don't have any linked account", }), expect.anything(), - ) - }) -}) + ); + }); +}); diff --git a/src/subcommands/osu/unlink.subcommand.ts b/src/subcommands/osu/unlink.subcommand.ts index 7dc8600..32d52ea 100644 --- a/src/subcommands/osu/unlink.subcommand.ts +++ b/src/subcommands/osu/unlink.subcommand.ts @@ -1,37 +1,38 @@ -import { type SlashCommandSubcommandBuilder } from "discord.js" -import type { OsuCommand } from "../../commands/osu.command" -import type { Subcommand } from "@sapphire/plugin-subcommands" -import { ExtendedError } from "../../lib/extended-error" +import type { Subcommand } from "@sapphire/plugin-subcommands"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; + +import type { OsuCommand } from "../../commands/osu.command"; +import { ExtendedError } from "../../lib/extended-error"; export function addUnlinkSubcommand(command: SlashCommandSubcommandBuilder) { - return command.setName("unlink").setDescription("Unlink your osu!sunrise profile") + return command.setName("unlink").setDescription("Unlink your osu!sunrise profile"); } export async function chatInputRunUnlinkSubcommand( this: OsuCommand, interaction: Subcommand.ChatInputCommandInteraction, ) { - await interaction.deferReply() + await interaction.deferReply(); - const { db } = this.container + const { db } = this.container; - var row = db.query("SELECT count(*) FROM connections WHERE discord_user_id = $1").get({ + const row = db.query("SELECT count(*) FROM connections WHERE discord_user_id = $1").get({ $1: interaction.user.id, - }) as { "count(*)": number } + }) as { "count(*)": number }; - const { embedPresets } = this.container.utilities + const { embedPresets } = this.container.utilities; if (!row || row["count(*)"] === 0) { - throw new ExtendedError(`❓ You don't have any linked account`) + throw new ExtendedError(`❓ You don't have any linked account`); } - const deleteConnection = db.prepare("DELETE FROM connections WHERE discord_user_id = $1") + const deleteConnection = db.prepare("DELETE FROM connections WHERE discord_user_id = $1"); deleteConnection.run({ $1: interaction.user.id, - }) + }); return await interaction.editReply({ embeds: [embedPresets.getSuccessEmbed(`I successfully unlinked your account!`)], - }) + }); } diff --git a/src/utilities/action-store.utility.ts b/src/utilities/action-store.utility.ts index 65a83d5..863bc2b 100644 --- a/src/utilities/action-store.utility.ts +++ b/src/utilities/action-store.utility.ts @@ -1,95 +1,98 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Utility } from "@sapphire/plugin-utilities-store" -import { Time } from "@sapphire/time-utilities" +import { ApplyOptions } from "@sapphire/decorators"; +import { Utility } from "@sapphire/plugin-utilities-store"; +import { Time } from "@sapphire/time-utilities"; interface CacheEntry { - data: T - expiresAt: number - timeout: NodeJS.Timeout + data: T; + expiresAt: number; + timeout: NodeJS.Timeout; } @ApplyOptions({ name: "actionStore" }) export class ActionStoreUtility extends Utility { - private localStore = new Map>() - private defaultTTL = Time.Minute * 5 - private maxEntriesLimit = 10_000 + private localStore = new Map>(); + private defaultTTL = Time.Minute * 5; + private maxEntriesLimit = 10_000; set(data: T, ttl?: number): string { - const id = Bun.randomUUIDv7() - const expiration = Date.now() + (ttl ?? this.defaultTTL) + const id = Bun.randomUUIDv7(); + const expiration = Date.now() + (ttl ?? this.defaultTTL); if (this.localStore.size >= this.maxEntriesLimit) { - this.cleanUp(1) + this.cleanUp(1); } if (this.localStore.has(id)) { - const entry = this.localStore.get(id)! - entry.data = data - this.updateExpiration(id, entry, expiration) - } else { + const entry = this.localStore.get(id)!; + entry.data = data; + this.updateExpiration(id, entry, expiration); + } + else { const timeout = setTimeout(() => { - this.localStore.delete(id) - }, ttl ?? this.defaultTTL) + this.localStore.delete(id); + }, ttl ?? this.defaultTTL); this.localStore.set(id, { data, expiresAt: expiration, timeout, - }) + }); } - return id + return id; } get(id: string): T | null { - const entry = this.localStore.get(id) - if (!entry) return null + const entry = this.localStore.get(id); + if (!entry) + return null; if (Date.now() > entry.expiresAt) { - this.delete(id) - return null + this.delete(id); + return null; } - this.updateExpiration(id, entry) + this.updateExpiration(id, entry); - return entry.data + return entry.data; } delete(id: string): boolean { - const entry = this.localStore.get(id) - if (!entry) return false + const entry = this.localStore.get(id); + if (!entry) + return false; - clearTimeout(entry.timeout) - return this.localStore.delete(id) + clearTimeout(entry.timeout); + return this.localStore.delete(id); } clear(): void { for (const [id, entry] of this.localStore.entries()) { - clearTimeout(entry.timeout) - this.localStore.delete(id) + clearTimeout(entry.timeout); + this.localStore.delete(id); } } private cleanUp(size: number): void { const entriesArray = Array.from(this.localStore.entries()).sort(([a], [b]) => a.localeCompare(b), - ) + ); - const entriesToRemove = entriesArray.slice(size) + const entriesToRemove = entriesArray.slice(size); for (const [id] of entriesToRemove) { - this.delete(id) + this.delete(id); } } - private updateExpiration(id: string, entry: CacheEntry, ttl?: number) { - ttl = ttl ?? this.defaultTTL - entry.expiresAt = Date.now() + ttl + private updateExpiration(id: string, entry: CacheEntry, ttl: number = this.defaultTTL) { + entry.expiresAt = Date.now() + ttl; - if (entry.timeout) clearTimeout(entry.timeout) + if (entry.timeout) + clearTimeout(entry.timeout); entry.timeout = setTimeout(() => { - this.store.delete(id) - }, ttl) + this.store.delete(id); + }, ttl); } } diff --git a/src/utilities/embed-presets.utility.ts b/src/utilities/embed-presets.utility.ts index 9a4a61a..4e2f2eb 100644 --- a/src/utilities/embed-presets.utility.ts +++ b/src/utilities/embed-presets.utility.ts @@ -1,26 +1,24 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Utility } from "@sapphire/plugin-utilities-store" +import { ApplyOptions } from "@sapphire/decorators"; +import { Utility } from "@sapphire/plugin-utilities-store"; +import type { HexColorString } from "discord.js"; +import { bold, EmbedBuilder, inlineCode, time } from "discord.js"; +import { getAverageColor } from "fast-average-color-node"; + +import { config } from "../lib/configs/env"; +import type { BeatmapResponse, CustomBeatmapStatusChangeResponse, ScoreResponse, UserResponse, UserStatsResponse } from "../lib/types/api"; import { GameMode, getBeatmapByIdPp, getUserByIdGrades, getUserByIdScores, ScoreTableType, - type BeatmapResponse, - type CustomBeatmapStatusChangeResponse, - type ScoreResponse, - type UserResponse, - type UserStatsResponse, -} from "../lib/types/api" -import { numberWith } from "../lib/utils/text-conversion/number-with.util" -import { bold, inlineCode, EmbedBuilder, type HexColorString, time } from "discord.js" -import { config } from "../lib/configs/env" -import { getDuration } from "../lib/utils/text-conversion/get-duration.util" -import { getAverageColor } from "fast-average-color-node" -import { tryToGetImage } from "../lib/utils/fetch.util" -import getBeatmapStatusIcon, { getScoreRankEmoji } from "../lib/utils/osu/emoji.util" -import { getBeatmapStarRating } from "../lib/utils/osu/star-rating.util" -import { secondsToMinutes } from "../lib/utils/text-conversion/seconds-to.util" +} from "../lib/types/api"; +import { tryToGetImage } from "../lib/utils/fetch.util"; +import getBeatmapStatusIcon, { getScoreRankEmoji } from "../lib/utils/osu/emoji.util"; +import { getBeatmapStarRating } from "../lib/utils/osu/star-rating.util"; +import { getDuration } from "../lib/utils/text-conversion/get-duration.util"; +import { numberWith } from "../lib/utils/text-conversion/number-with.util"; +import { secondsToMinutes } from "../lib/utils/text-conversion/seconds-to.util"; @ApplyOptions({ name: "embedPresets" }) export class EmbedPresetsUtility extends Utility { @@ -29,8 +27,8 @@ export class EmbedPresetsUtility extends Utility { .setTitle(Title.slice(0, 255)) .setDescription(message ?? null) .setColor("Green") - .setTimestamp() - return createEmbed + .setTimestamp(); + return createEmbed; } public getErrorEmbed(Title: string, message?: string) { @@ -39,8 +37,8 @@ export class EmbedPresetsUtility extends Utility { .setDescription(message ?? null) .setColor("Red") .setFooter({ text: "Im sorry! >.<" }) - .setTimestamp() - return createEmbed + .setTimestamp(); + return createEmbed; } public async getUserEmbed(user: UserResponse, stats: UserStatsResponse) { @@ -57,27 +55,27 @@ export class EmbedPresetsUtility extends Utility { path: { id: user.user_id }, query: { mode: stats.gamemode }, }), - ]) + ]); if (!topScoresResult || topScoresResult.error) { - throw new Error("EmbedPresetsUtility: Couldn't fetch user's top scores") + throw new Error("EmbedPresetsUtility: Couldn't fetch user's top scores"); } if (!userGradesResult || userGradesResult.error) { - throw new Error("EmbedPresetsUtility: Couldn't fetch user's grades") + throw new Error("EmbedPresetsUtility: Couldn't fetch user's grades"); } - const firstPlacesCount = topScoresResult.data?.total_count ?? 0 + const firstPlacesCount = topScoresResult.data?.total_count ?? 0; - const color = await getAverageColor(user.avatar_url) + const color = await getAverageColor(user.avatar_url); - const lastOnlineTime = new Date(user.last_online_time) - const registerTime = new Date(user.register_date) + const lastOnlineTime = new Date(user.last_online_time); + const registerTime = new Date(user.register_date); - const userStatus = - user.user_status == "Offline" + const userStatus + = user.user_status === "Offline" ? `🍂 ${bold(`Offline.`)}\n` + `Last time online: ${bold(time(lastOnlineTime, "R"))}` - : `🌿 ${bold(user.user_status)}` + : `🌿 ${bold(user.user_status)}`; const infoValues = [ { name: "Status", value: userStatus }, @@ -88,16 +86,16 @@ export class EmbedPresetsUtility extends Utility { { name: "Current rank", value: - `${bold("#" + stats.rank)}` + - " " + - `(:flag_${user.country_code.toLowerCase()}: #${stats.country_rank})`, + `${bold(`#${stats.rank}`)}` + + " " + + `(:flag_${user.country_code.toLowerCase()}: #${stats.country_rank})`, }, { name: "Peak rank", value: - `${bold("#" + stats.best_global_rank)}` + - " " + - `(:flag_${user.country_code.toLowerCase()}: #${stats.best_country_rank})`, + `${bold(`#${stats.best_global_rank}`)}` + + " " + + `(:flag_${user.country_code.toLowerCase()}: #${stats.best_country_rank})`, }, { name: null, value: "" }, { @@ -113,9 +111,9 @@ export class EmbedPresetsUtility extends Utility { { name: "Playcount", value: - inlineCode(numberWith(stats.play_count, ",")) + - " " + - `(${inlineCode(getDuration(stats.play_time / 1000))})`, + `${inlineCode(numberWith(stats.play_count, ",")) + } ` + + `(${inlineCode(getDuration(stats.play_time / 1000))})`, }, { name: "Total score", @@ -127,24 +125,27 @@ export class EmbedPresetsUtility extends Utility { name: "Ranked score", value: inlineCode(numberWith(stats.ranked_score, ",")), }, - ] + ]; const description = infoValues.reduce((pr, cur) => { if (cur.name) { - pr += `${cur.name}: ` + // eslint-disable-next-line no-param-reassign -- reasonable here + pr += `${cur.name}: `; } - pr += cur.value + // eslint-disable-next-line no-param-reassign -- reasonable here + pr += cur.value; if (cur.newLine !== false) { - pr += "\n" + // eslint-disable-next-line no-param-reassign -- reasonable here + pr += "\n"; } - return pr - }, "") + return pr; + }, ""); - const { A, S, SH, X, XH } = this.container.config.json.emojis.ranks - const { count_a, count_s, count_sh, count_x, count_xh } = userGradesResult.data + const { A, S, SH, X, XH } = this.container.config.json.emojis.ranks; + const { count_a, count_s, count_sh, count_x, count_xh } = userGradesResult.data; const userEmbed = new EmbedBuilder() .setAuthor({ @@ -169,15 +170,15 @@ export class EmbedPresetsUtility extends Utility { ]) .setFooter({ text: `${stats.gamemode} · osu!sunrise`, - }) + }); - return userEmbed + return userEmbed; } public async getScoreEmbed( score: ScoreResponse, beatmap: BeatmapResponse, - isScoreNew: boolean = false, + isScoreNew = false, ) { if (score.mods_int && score.mods_int > 0) { const pp = await getBeatmapByIdPp({ @@ -188,57 +189,57 @@ export class EmbedPresetsUtility extends Utility { mods: score.mods_int as any, mode: score.game_mode, }, - }) + }); if (!pp || pp.error) { - throw new Error("EmbedPresetsUtility: Couldn't fetch beatmaps modded star rating") + throw new Error("EmbedPresetsUtility: Couldn't fetch beatmaps modded star rating"); } - beatmap.star_rating_ctb = Number(pp.data.difficulty.stars.toFixed(2)) - beatmap.star_rating_mania = Number(pp.data.difficulty.stars.toFixed(2)) - beatmap.star_rating_osu = Number(pp.data.difficulty.stars.toFixed(2)) - beatmap.star_rating_taiko = Number(pp.data.difficulty.stars.toFixed(2)) + beatmap.star_rating_ctb = Number(pp.data.difficulty.stars.toFixed(2)); + beatmap.star_rating_mania = Number(pp.data.difficulty.stars.toFixed(2)); + beatmap.star_rating_osu = Number(pp.data.difficulty.stars.toFixed(2)); + beatmap.star_rating_taiko = Number(pp.data.difficulty.stars.toFixed(2)); } const beatmapBannerImage = await tryToGetImage( `https://assets.ppy.sh/beatmaps/${beatmap.beatmapset_id}/covers/list@2x.jpg`, - ) + ); - const color = await getAverageColor(beatmapBannerImage) + const color = await getAverageColor(beatmapBannerImage); - const whenPlayedDate = new Date(score.when_played) + const whenPlayedDate = new Date(score.when_played); - var titleText = isScoreNew ? "new score submission" : "submitted score" + const titleText = isScoreNew ? "new score submission" : "submitted score"; - let hitCounts = null + let hitCounts = null; switch (score.game_mode) { case GameMode.STANDARD: - hitCounts = `[${score.count_300}/${score.count_100}/${score.count_50}/${score.count_miss}]` - break + hitCounts = `[${score.count_300}/${score.count_100}/${score.count_50}/${score.count_miss}]`; + break; case GameMode.TAIKO: - hitCounts = `[${score.count_300}/${score.count_100}/${score.count_miss}]` - break + hitCounts = `[${score.count_300}/${score.count_100}/${score.count_miss}]`; + break; case GameMode.CATCH_THE_BEAT: - hitCounts = `[${score.count_300}/${score.count_100}/${score.count_50}/${score.count_miss}]` - break + hitCounts = `[${score.count_300}/${score.count_100}/${score.count_50}/${score.count_miss}]`; + break; case GameMode.MANIA: - hitCounts = `[${score.count_geki}/${score.count_300}/${score.count_katu}/${score.count_100}/${score.count_50}/${score.count_miss}]` - break + hitCounts = `[${score.count_geki}/${score.count_300}/${score.count_katu}/${score.count_100}/${score.count_50}/${score.count_miss}]`; + break; } - const description = - `${getScoreRankEmoji(score.grade)} ${score.mods}` + - " · " + - numberWith(score.total_score, ",") + - " · " + - ` ${score.accuracy.toFixed(2)}% ${bold(time(whenPlayedDate, "R"))}` + - "\n" + - `${bold(beatmap.is_ranked ? score.performance_points.toFixed(2) : "~ ")}pp` + - " · " + - `${bold("x" + score.max_combo)} / ${beatmap.max_combo}` + - " · " + - hitCounts + const description + = `${getScoreRankEmoji(score.grade)} ${score.mods}` + + ` · ${ + numberWith(score.total_score, ",") + } · ` + + ` ${score.accuracy.toFixed(2)}% ${bold(time(whenPlayedDate, "R"))}` + + `\n` + + `${bold(beatmap.is_ranked ? score.performance_points.toFixed(2) : "~ ")}pp` + + ` · ` + + `${bold(`x${score.max_combo}`)} / ${beatmap.max_combo}` + + ` · ${ + hitCounts}`; const scoreEmbed = new EmbedBuilder() .setAuthor({ @@ -258,29 +259,29 @@ export class EmbedPresetsUtility extends Utility { .setFooter({ text: `${score.game_mode_extended} · played on osu!sunrise`, }) - .setDescription(description) + .setDescription(description); - return scoreEmbed + return scoreEmbed; } public async getCustomBeatmapStatusChangeEmbed(data: CustomBeatmapStatusChangeResponse) { - const { bat, beatmap, new_status, old_status } = data + const { bat, beatmap, new_status, old_status } = data; const beatmapBannerImage = await tryToGetImage( `https://assets.ppy.sh/beatmaps/${beatmap.beatmapset_id}/covers/cover@2x.jpg`, "https://osu.ppy.sh/assets/images/default-bg.7594e945.png", - ) + ); - const color = await getAverageColor(beatmapBannerImage) + const color = await getAverageColor(beatmapBannerImage); const pp = await getBeatmapByIdPp({ path: { id: beatmap.id, }, - }) + }); if (!pp || pp.error) { - throw new Error("EmbedPresetsUtility: Couldn't fetch beatmap performance data") + throw new Error("EmbedPresetsUtility: Couldn't fetch beatmap performance data"); } const ppFields = beatmap.ranked @@ -296,7 +297,7 @@ export class EmbedPresetsUtility extends Utility { inline: true, }, ] - : [] + : []; const scoreEmbed = new EmbedBuilder() .setAuthor({ @@ -306,9 +307,9 @@ export class EmbedPresetsUtility extends Utility { }) .setColor(`${color.hex}` as HexColorString) .setTitle( - `${beatmap.artist} - ${beatmap.title}` + - " " + - `[${beatmap.version}] [★${getBeatmapStarRating(beatmap, beatmap.mode)}]`, + `${beatmap.artist} - ${beatmap.title}` + + " " + + `[${beatmap.version}] [★${getBeatmapStarRating(beatmap, beatmap.mode)}]`, ) .setImage(beatmapBannerImage) .setURL(`https://${config.sunrise.uri}/beatmaps/${beatmap.id}`) @@ -334,10 +335,7 @@ export class EmbedPresetsUtility extends Utility { }, { name: "Length", - value: `${config.json.emojis.totalLengthIcon} ${secondsToMinutes(beatmap.total_length, { - asHours: true, - pad: true, - })}`, + value: `${config.json.emojis.totalLengthIcon} ${secondsToMinutes(beatmap.total_length)}`, inline: true, }, { @@ -360,8 +358,8 @@ export class EmbedPresetsUtility extends Utility { value: `${config.json.emojis.countSlidersIcon} ${beatmap.count_sliders}`, inline: true, }, - ) + ); - return scoreEmbed + return scoreEmbed; } } diff --git a/src/utilities/pagination.utility.ts b/src/utilities/pagination.utility.ts index 264c8c7..a20be52 100644 --- a/src/utilities/pagination.utility.ts +++ b/src/utilities/pagination.utility.ts @@ -1,94 +1,96 @@ -import { ApplyOptions } from "@sapphire/decorators" -import { Utility } from "@sapphire/plugin-utilities-store" - +import { ApplyOptions } from "@sapphire/decorators"; +import { Utility } from "@sapphire/plugin-utilities-store"; +import type { + ChatInputCommandInteraction, + EmbedBuilder, +} from "discord.js"; import { ActionRowBuilder, - ButtonStyle, ButtonBuilder, - ChatInputCommandInteraction, - EmbedBuilder, -} from "discord.js" -import { PaginationButtonAction } from "../lib/types/enum/pagination.types" -import { buildCustomId } from "../lib/utils/discord.util" -import type { PaginationStore } from "../lib/types/store.types" -import { PaginationInteractionCustomId } from "../lib/types/enum/custom-ids.types" + ButtonStyle, +} from "discord.js"; + +import { PaginationInteractionCustomId } from "../lib/types/enum/custom-ids.types"; +import { PaginationButtonAction } from "../lib/types/enum/pagination.types"; +import type { PaginationStore } from "../lib/types/store.types"; +import { buildCustomId } from "../lib/utils/discord.util"; @ApplyOptions({ name: "pagination" }) export class PaginationUtility extends Utility { - private readonly buttonMaxLeft = new ButtonBuilder().setLabel("⏮️").setStyle(ButtonStyle.Primary) - private readonly buttonLeft = new ButtonBuilder().setLabel("⬅️").setStyle(ButtonStyle.Primary) + private readonly buttonMaxLeft = new ButtonBuilder().setLabel("⏮️").setStyle(ButtonStyle.Primary); + private readonly buttonLeft = new ButtonBuilder().setLabel("⬅️").setStyle(ButtonStyle.Primary); - private readonly buttonRight = new ButtonBuilder().setLabel("➡️").setStyle(ButtonStyle.Primary) - private readonly buttonMaxRight = new ButtonBuilder().setLabel("⏭️").setStyle(ButtonStyle.Primary) + private readonly buttonRight = new ButtonBuilder().setLabel("➡️").setStyle(ButtonStyle.Primary); + private readonly buttonMaxRight = new ButtonBuilder().setLabel("⏭️").setStyle(ButtonStyle.Primary); private readonly buttonSelectPage = new ButtonBuilder() .setLabel("*️⃣") - .setStyle(ButtonStyle.Primary) + .setStyle(ButtonStyle.Primary); public async createPaginationHandler( interaction: ChatInputCommandInteraction, handleSetPage: (state: { - pageSize: number - totalPages: number - currentPage: number + pageSize: number; + totalPages: number; + currentPage: number; }) => Promise, initState?: { - pageSize?: number - totalPages?: number - currentPage?: number + pageSize?: number; + totalPages?: number; + currentPage?: number; }, ) { const state = { pageSize: initState?.pageSize ?? 1, currentPage: initState?.currentPage ?? 1, totalPages: initState?.totalPages ?? 1, - } + }; - const updatedEmbed = await handleSetPage(state) + const updatedEmbed = await handleSetPage(state); const handleSetPageWrapped = async (state: { - pageSize: number - totalPages: number - currentPage: number + pageSize: number; + totalPages: number; + currentPage: number; }) => ({ buttonsRow: this.getPaginationButtonsRow(interaction.user.id, storeId, state), embed: await handleSetPage(state), - }) + }); const storeId = this.container.utilities.actionStore.set({ handleSetPage: handleSetPageWrapped, state, - }) + }); - const buttonsRow = this.getPaginationButtonsRow(interaction.user.id, storeId, state) + const buttonsRow = this.getPaginationButtonsRow(interaction.user.id, storeId, state); await interaction.editReply({ embeds: [updatedEmbed], components: [buttonsRow], - }) + }); } private getPaginationButtonsRow( userId: string, storeId: string, options: { - pageSize: number - currentPage: number - totalPages: number + pageSize: number; + currentPage: number; + totalPages: number; }, ) { - const buttonsRow = new ActionRowBuilder() + const buttonsRow = new ActionRowBuilder(); - const { currentPage, totalPages } = options + const { currentPage, totalPages } = options; - const isOnFirstPage = currentPage <= 1 - const isOnLastPage = currentPage >= totalPages + const isOnFirstPage = currentPage <= 1; + const isOnLastPage = currentPage >= totalPages; const getCustomPaginationButtonId = (buttonData: string) => buildCustomId(PaginationInteractionCustomId.PAGINATION_ACTION_MOVE, userId, { dataStoreId: storeId, data: [buttonData], - }) + }); buttonsRow.setComponents( ButtonBuilder.from(this.buttonMaxLeft.toJSON()) @@ -101,7 +103,7 @@ export class PaginationUtility extends Utility { ButtonBuilder.from(this.buttonSelectPage.toJSON()) .setCustomId(getCustomPaginationButtonId(PaginationButtonAction.SELECT_PAGE)) .setDisabled(isOnFirstPage && isOnLastPage), - ) + ); buttonsRow.addComponents( ButtonBuilder.from(this.buttonRight.toJSON()) @@ -110,8 +112,8 @@ export class PaginationUtility extends Utility { ButtonBuilder.from(this.buttonMaxRight.toJSON()) .setCustomId(getCustomPaginationButtonId(PaginationButtonAction.MAX_RIGHT)) .setDisabled(isOnLastPage), - ) + ); - return buttonsRow + return buttonsRow; } } diff --git a/src/utilities/tests/action-store.utility.test.ts b/src/utilities/tests/action-store.utility.test.ts index 35e7ec4..94b614a 100644 --- a/src/utilities/tests/action-store.utility.test.ts +++ b/src/utilities/tests/action-store.utility.test.ts @@ -1,294 +1,295 @@ -import { expect, describe, it, beforeAll, afterAll, beforeEach } from "bun:test" -import { container } from "@sapphire/framework" -import { Mocker } from "../../lib/mock/mocker" -import { ActionStoreUtility } from "../action-store.utility" -import { faker } from "@faker-js/faker" -import { Time } from "@sapphire/time-utilities" +import { faker } from "@faker-js/faker"; +import { container } from "@sapphire/framework"; +import { Time } from "@sapphire/time-utilities"; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "bun:test"; + +import { Mocker } from "../../lib/mock/mocker"; +import type { ActionStoreUtility } from "../action-store.utility"; describe("Action Store Utility", () => { - let actionStore: ActionStoreUtility + let actionStore: ActionStoreUtility; beforeAll(() => { - Mocker.createSapphireClientInstance() - actionStore = container.utilities.actionStore - }) + Mocker.createSapphireClientInstance(); + actionStore = container.utilities.actionStore; + }); afterAll(async () => { - await Mocker.resetSapphireClientInstance() - }) + await Mocker.resetSapphireClientInstance(); + }); - beforeEach(() => actionStore.clear()) + beforeEach(() => actionStore.clear()); describe("set", () => { it("should store data and return a unique ID", () => { - const testData = { foo: "bar" } - const id = actionStore.set(testData) + const testData = { foo: "bar" }; + const id = actionStore.set(testData); - expect(id).toBeString() - expect(id.length).toBeGreaterThan(0) - }) + expect(id).toBeString(); + expect(id.length).toBeGreaterThan(0); + }); it("should store different types of data", () => { - const stringData = "test string" - const numberData = 42 - const objectData = { key: "value" } - const arrayData = [1, 2, 3] - - const id1 = actionStore.set(stringData) - const id2 = actionStore.set(numberData) - const id3 = actionStore.set(objectData) - const id4 = actionStore.set(arrayData) - - expect(actionStore.get(id1)).toBe(stringData) - expect(actionStore.get(id2)).toBe(numberData) - expect(actionStore.get(id3)).toEqual(objectData) - expect(actionStore.get(id4)).toEqual(arrayData) - }) + const stringData = "test string"; + const numberData = 42; + const objectData = { key: "value" }; + const arrayData = [1, 2, 3]; + + const id1 = actionStore.set(stringData); + const id2 = actionStore.set(numberData); + const id3 = actionStore.set(objectData); + const id4 = actionStore.set(arrayData); + + expect(actionStore.get(id1)).toBe(stringData); + expect(actionStore.get(id2)).toBe(numberData); + expect(actionStore.get(id3)).toEqual(objectData); + expect(actionStore.get(id4)).toEqual(arrayData); + }); it("should generate unique IDs for each entry", () => { - const id1 = actionStore.set("data1") - const id2 = actionStore.set("data2") - const id3 = actionStore.set("data3") + const id1 = actionStore.set("data1"); + const id2 = actionStore.set("data2"); + const id3 = actionStore.set("data3"); - expect(id1).not.toBe(id2) - expect(id2).not.toBe(id3) - expect(id1).not.toBe(id3) - }) + expect(id1).not.toBe(id2); + expect(id2).not.toBe(id3); + expect(id1).not.toBe(id3); + }); it("should accept custom TTL", () => { - const testData = { custom: "ttl" } - const customTTL = Time.Second * 1 - const id = actionStore.set(testData, customTTL) + const testData = { custom: "ttl" }; + const customTTL = Time.Second * 1; + const id = actionStore.set(testData, customTTL); - expect(actionStore.get(id)).toEqual(testData) - }) - }) + expect(actionStore.get(id)).toEqual(testData); + }); + }); describe("get", () => { it("should retrieve stored data by ID", () => { - const testData = { test: "data" } - const id = actionStore.set(testData) + const testData = { test: "data" }; + const id = actionStore.set(testData); - const retrieved = actionStore.get(id) + const retrieved = actionStore.get(id); - expect(retrieved).toEqual(testData) - }) + expect(retrieved).toEqual(testData); + }); it("should return null for non-existent ID", () => { - const fakeId = faker.string.uuid() - const result = actionStore.get(fakeId) + const fakeId = faker.string.uuid(); + const result = actionStore.get(fakeId); - expect(result).toBeNull() - }) + expect(result).toBeNull(); + }); it("should return null for expired entries", async () => { - const testData = { expires: "soon" } - const shortTTL = Time.Millisecond * 100 - const id = actionStore.set(testData, shortTTL) + const testData = { expires: "soon" }; + const shortTTL = Time.Millisecond * 100; + const id = actionStore.set(testData, shortTTL); // Wait for expiration - await new Promise((resolve) => setTimeout(resolve, 150)) + await new Promise(resolve => setTimeout(resolve, 150)); - const result = actionStore.get(id) + const result = actionStore.get(id); - expect(result).toBeNull() - }) + expect(result).toBeNull(); + }); it("should refresh expiration on get", async () => { - const testData = { refresh: "test" } - const ttl = Time.Millisecond * 200 - const id = actionStore.set(testData, ttl) + const testData = { refresh: "test" }; + const ttl = Time.Millisecond * 200; + const id = actionStore.set(testData, ttl); // Wait 100ms, then get (should refresh) - await new Promise((resolve) => setTimeout(resolve, Time.Millisecond * 100)) - const result1 = actionStore.get(id) - expect(result1).toEqual(testData) + await new Promise(resolve => setTimeout(resolve, Time.Millisecond * 100)); + const result1 = actionStore.get(id); + expect(result1).toEqual(testData); // Wait another 100ms, data should still be there (refreshed) - await new Promise((resolve) => setTimeout(resolve, Time.Millisecond * 100)) - const result2 = actionStore.get(id) - expect(result2).toEqual(testData) - }) - }) + await new Promise(resolve => setTimeout(resolve, Time.Millisecond * 100)); + const result2 = actionStore.get(id); + expect(result2).toEqual(testData); + }); + }); describe("delete", () => { it("should delete an entry and return true", () => { - const testData = { to: "delete" } - const id = actionStore.set(testData) + const testData = { to: "delete" }; + const id = actionStore.set(testData); - const deleted = actionStore.delete(id) + const deleted = actionStore.delete(id); - expect(deleted).toBe(true) - expect(actionStore.get(id)).toBeNull() - }) + expect(deleted).toBe(true); + expect(actionStore.get(id)).toBeNull(); + }); it("should return false for non-existent ID", () => { - const fakeId = faker.string.uuid() - const deleted = actionStore.delete(fakeId) + const fakeId = faker.string.uuid(); + const deleted = actionStore.delete(fakeId); - expect(deleted).toBe(false) - }) + expect(deleted).toBe(false); + }); it("should clear timeout when deleting", () => { - const testData = { with: "timeout" } - const id = actionStore.set(testData) + const testData = { with: "timeout" }; + const id = actionStore.set(testData); - actionStore.delete(id) + actionStore.delete(id); - expect(actionStore.get(id)).toBeNull() - }) + expect(actionStore.get(id)).toBeNull(); + }); it("should allow deleting the same ID multiple times", () => { - const testData = { double: "delete" } - const id = actionStore.set(testData) + const testData = { double: "delete" }; + const id = actionStore.set(testData); - const deleted1 = actionStore.delete(id) - const deleted2 = actionStore.delete(id) + const deleted1 = actionStore.delete(id); + const deleted2 = actionStore.delete(id); - expect(deleted1).toBe(true) - expect(deleted2).toBe(false) - }) - }) + expect(deleted1).toBe(true); + expect(deleted2).toBe(false); + }); + }); describe("clear", () => { it("should remove all entries", () => { - const id1 = actionStore.set("data1") - const id2 = actionStore.set("data2") - const id3 = actionStore.set("data3") + const id1 = actionStore.set("data1"); + const id2 = actionStore.set("data2"); + const id3 = actionStore.set("data3"); - actionStore.clear() + actionStore.clear(); - expect(actionStore.get(id1)).toBeNull() - expect(actionStore.get(id2)).toBeNull() - expect(actionStore.get(id3)).toBeNull() - }) + expect(actionStore.get(id1)).toBeNull(); + expect(actionStore.get(id2)).toBeNull(); + expect(actionStore.get(id3)).toBeNull(); + }); it("should clear timeouts for all entries", async () => { - const id1 = actionStore.set("data1", Time.Second * 5) - const id2 = actionStore.set("data2", Time.Second * 5) + const id1 = actionStore.set("data1", Time.Second * 5); + const id2 = actionStore.set("data2", Time.Second * 5); - actionStore.clear() + actionStore.clear(); // Wait a bit to ensure timeouts were cleared - await new Promise((resolve) => setTimeout(resolve, Time.Millisecond * 50)) + await new Promise(resolve => setTimeout(resolve, Time.Millisecond * 50)); - expect(actionStore.get(id1)).toBeNull() - expect(actionStore.get(id2)).toBeNull() - }) + expect(actionStore.get(id1)).toBeNull(); + expect(actionStore.get(id2)).toBeNull(); + }); it("should work on empty store", () => { - expect(() => actionStore.clear()).not.toThrow() - }) - }) + expect(() => actionStore.clear()).not.toThrow(); + }); + }); describe("expiration", () => { it("should automatically delete entries after TTL", async () => { - const testData = { auto: "expire" } - const shortTTL = Time.Millisecond * 100 - const id = actionStore.set(testData, shortTTL) + const testData = { auto: "expire" }; + const shortTTL = Time.Millisecond * 100; + const id = actionStore.set(testData, shortTTL); // Wait for expiration (without calling get, which would refresh) - await new Promise((resolve) => setTimeout(resolve, Time.Millisecond * 150)) + await new Promise(resolve => setTimeout(resolve, Time.Millisecond * 150)); // Data should be gone - expect(actionStore.get(id)).toBeNull() - }) + expect(actionStore.get(id)).toBeNull(); + }); it("should handle multiple entries with different TTLs", async () => { - const data1 = { ttl: "short" } - const data2 = { ttl: "long" } + const data1 = { ttl: "short" }; + const data2 = { ttl: "long" }; - const id1 = actionStore.set(data1, Time.Millisecond * 100) - const id2 = actionStore.set(data2, Time.Millisecond * 400) + const id1 = actionStore.set(data1, Time.Millisecond * 100); + const id2 = actionStore.set(data2, Time.Millisecond * 400); // Wait for first to expire (without calling get, which would refresh) - await new Promise((resolve) => setTimeout(resolve, Time.Millisecond * 150)) + await new Promise(resolve => setTimeout(resolve, Time.Millisecond * 150)); - expect(actionStore.get(id1)).toBeNull() + expect(actionStore.get(id1)).toBeNull(); // Wait for second to expire - await new Promise((resolve) => setTimeout(resolve, Time.Millisecond * 300)) + await new Promise(resolve => setTimeout(resolve, Time.Millisecond * 300)); - expect(actionStore.get(id2)).toBeNull() - }) - }) + expect(actionStore.get(id2)).toBeNull(); + }); + }); describe("edge cases", () => { it("should handle storing null values", () => { - const id = actionStore.set(null) - const result = actionStore.get(id) + const id = actionStore.set(null); + const result = actionStore.get(id); - expect(result).toBeNull() - }) + expect(result).toBeNull(); + }); it("should handle storing undefined values", () => { - const id = actionStore.set(undefined) - const result = actionStore.get(id) + const id = actionStore.set(undefined); + const result = actionStore.get(id); - expect(result).toBeUndefined() - }) + expect(result).toBeUndefined(); + }); it("should handle storing empty objects", () => { - const emptyObj = {} - const id = actionStore.set(emptyObj) - const result = actionStore.get(id) + const emptyObj = {}; + const id = actionStore.set(emptyObj); + const result = actionStore.get(id); - expect(result).toEqual({}) - }) + expect(result).toEqual({}); + }); it("should handle storing empty arrays", () => { - const emptyArr: any[] = [] - const id = actionStore.set(emptyArr) - const result = actionStore.get(id) + const emptyArr: any[] = []; + const id = actionStore.set(emptyArr); + const result = actionStore.get(id); - expect(result).toEqual([]) - }) + expect(result).toEqual([]); + }); it("should handle zero TTL", async () => { - const testData = { ttl: "zero" } - const id = actionStore.set(testData, 0) + const testData = { ttl: "zero" }; + const id = actionStore.set(testData, 0); // Wait for the immediate timeout to fire - await new Promise((resolve) => setTimeout(resolve, 10)) + await new Promise(resolve => setTimeout(resolve, 10)); // Should be expired - const result = actionStore.get(id) - expect(result).toBeNull() - }) + const result = actionStore.get(id); + expect(result).toBeNull(); + }); it("should handle very large TTL", () => { - const testData = { ttl: "large" } - const largeTTL = Time.Day * 20 - const id = actionStore.set(testData, largeTTL) + const testData = { ttl: "large" }; + const largeTTL = Time.Day * 20; + const id = actionStore.set(testData, largeTTL); - const result = actionStore.get(id) - expect(result).toEqual(testData) - }) - }) + const result = actionStore.get(id); + expect(result).toEqual(testData); + }); + }); describe("data isolation", () => { it("should not modify original data when retrieved", () => { - const originalData = { nested: { value: "original" } } - const id = actionStore.set(originalData) + const originalData = { nested: { value: "original" } }; + const id = actionStore.set(originalData); - const retrieved = actionStore.get(id) + const retrieved = actionStore.get(id); if (retrieved) { - retrieved.nested.value = "modified" + retrieved.nested.value = "modified"; } - const retrievedAgain = actionStore.get(id) - expect(retrievedAgain?.nested.value).toBe("modified") - }) + const retrievedAgain = actionStore.get(id); + expect(retrievedAgain?.nested.value).toBe("modified"); + }); it("should store independent copies for different IDs", () => { - const data1 = { value: 1 } - const data2 = { value: 2 } + const data1 = { value: 1 }; + const data2 = { value: 2 }; - const id1 = actionStore.set(data1) - const id2 = actionStore.set(data2) + const id1 = actionStore.set(data1); + const id2 = actionStore.set(data2); - expect(actionStore.get<{ value: number }>(id1)).toEqual({ value: 1 }) - expect(actionStore.get<{ value: number }>(id2)).toEqual({ value: 2 }) - }) - }) -}) + expect(actionStore.get<{ value: number }>(id1)).toEqual({ value: 1 }); + expect(actionStore.get<{ value: number }>(id2)).toEqual({ value: 2 }); + }); + }); +});