diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..5665388 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,24 @@ +export default { + preset: 'ts-jest/presets/default-esm', + extensionsToTreatAsEsm: ['.ts'], + testEnvironment: 'node', + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + transform: { + '^.+\\.tsx?$': ['ts-jest', { + useESM: true, + }], + }, + testMatch: [ + '**/__tests__/**/*.test.ts', + '**/?(*.)+(spec|test).ts', + ], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/__tests__/**', + ], + setupFilesAfterEnv: ['/jest.setup.js'], +}; \ No newline at end of file diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..cd2f88f --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,2 @@ +// Jest setup file +// This file is loaded before tests run \ No newline at end of file diff --git a/package.json b/package.json index d8b21ff..316ca22 100644 --- a/package.json +++ b/package.json @@ -9,17 +9,29 @@ "build": "tsc", "dev": "node --loader ts-node/esm/transpile-only src/main.ts", "start": "node build/main.js", - "watch": "nodemon --exec npm run dev --watch src --ext ts" + "watch": "nodemon --exec npm run dev --watch src --ext ts", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" }, "dependencies": { "@discordx/importer": "^1.3.1", + "cacheables": "^2.0.0", "discord.js": "^14.17.3", - "discordx": "^11.12.2" + "discordx": "^11.12.2", + "dotenv": "^17.1.0", + "mongoose": "^8.15.1", + "mongoose-long": "^0.8.0", + "pino": "^9.7.0" }, "devDependencies": { + "@jest/globals": "^30.1.2", + "@types/jest": "^30.0.0", "@types/node": "^22.10.10", + "jest": "^30.1.3", "nodemon": "^3.1.9", "prettier": "^3.4.2", + "ts-jest": "^29.4.2", "ts-node": "^10.9.2", "typescript": "5.7.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5c9e67..8e1694d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,22 +11,49 @@ importers: '@discordx/importer': specifier: ^1.3.1 version: 1.3.2 + cacheables: + specifier: ^2.0.0 + version: 2.0.0 discord.js: specifier: ^14.17.3 version: 14.19.3 discordx: specifier: ^11.12.2 version: 11.12.4(discord.js@14.19.3) + dotenv: + specifier: ^17.1.0 + version: 17.1.0 + mongoose: + specifier: ^8.15.1 + version: 8.15.1 + mongoose-long: + specifier: ^0.8.0 + version: 0.8.0(mongoose@8.15.1) + pino: + specifier: ^9.7.0 + version: 9.7.0 devDependencies: + '@jest/globals': + specifier: ^30.1.2 + version: 30.1.2 + '@types/jest': + specifier: ^30.0.0 + version: 30.0.0 '@types/node': specifier: ^22.10.10 version: 22.15.31 + jest: + specifier: ^30.1.3 + version: 30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)) nodemon: specifier: ^3.1.9 version: 3.1.10 prettier: specifier: ^3.4.2 version: 3.5.3 + ts-jest: + specifier: ^29.4.2 + version: 29.4.2(@babel/core@7.28.4)(@jest/transform@30.1.2)(@jest/types@30.0.5)(babel-jest@30.1.2(@babel/core@7.28.4))(jest-util@30.0.5)(jest@30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)))(typescript@5.7.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.15.31)(typescript@5.7.3) @@ -36,6 +63,171 @@ importers: packages: + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.4': + resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.4': + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.4': + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -79,6 +271,15 @@ packages: resolution: {integrity: sha512-Wpr1bh9GpTud7+fRlHUSihf2zrFzXgSC+8OCvwzODSH794mwWhKRYjGQKMgk3xfOmqiP8FTzs0/5GfYSbCOQ0Q==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} + '@emnapi/core@1.5.0': + resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} + + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -91,6 +292,102 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@30.1.2': + resolution: {integrity: sha512-BGMAxj8VRmoD0MoA/jo9alMXSRoqW8KPeqOfEo1ncxnRLatTBCpRoOwlwlEMdudp68Q6WSGwYrrLtTGOh8fLzw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/core@30.1.3': + resolution: {integrity: sha512-LIQz7NEDDO1+eyOA2ZmkiAyYvZuo6s1UxD/e2IHldR6D7UYogVq3arTmli07MkENLq6/3JEQjp0mA8rrHHJ8KQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/diff-sequences@30.0.1': + resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment@30.1.2': + resolution: {integrity: sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.1.2': + resolution: {integrity: sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.1.2': + resolution: {integrity: sha512-tyaIExOwQRCxPCGNC05lIjWJztDwk2gPDNSDGg1zitXJJ8dC3++G/CRjE5mb2wQsf89+lsgAgqxxNpDLiCViTA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/fake-timers@30.1.2': + resolution: {integrity: sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/globals@30.1.2': + resolution: {integrity: sha512-teNTPZ8yZe3ahbYnvnVRDeOjr+3pu2uiAtNtrEsiMjVPPj+cXd5E/fr8BL7v/T7F31vYdEHrI5cC/2OoO/vM9A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/reporters@30.1.3': + resolution: {integrity: sha512-VWEQmJWfXMOrzdFEOyGjUEOuVXllgZsoPtEHZzfdNz18RmzJ5nlR6kp8hDdY8dDS1yGOXAY7DHT+AOHIPSBV0w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.1.2': + resolution: {integrity: sha512-vHoMTpimcPSR7OxS2S0V1Cpg8eKDRxucHjoWl5u4RQcnxqQrV3avETiFpl8etn4dqxEGarBeHbIBety/f8mLXw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/source-map@30.0.1': + resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-result@30.1.3': + resolution: {integrity: sha512-P9IV8T24D43cNRANPPokn7tZh0FAFnYS2HIfi5vK18CjRkTDR9Y3e1BoEcAJnl4ghZZF4Ecda4M/k41QkvurEQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-sequencer@30.1.3': + resolution: {integrity: sha512-82J+hzC0qeQIiiZDThh+YUadvshdBswi5nuyXlEmXzrhw5ZQSRHeQ5LpVMD/xc8B3wPePvs6VMzHnntxL+4E3w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/transform@30.1.2': + resolution: {integrity: sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.0.5': + resolution: {integrity: sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -98,9 +395,26 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mongodb-js/saslprep@1.3.0': + resolution: {integrity: sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==} + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@sapphire/async-queue@1.5.5': resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -113,6 +427,15 @@ packages: resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@sinclair/typebox@0.34.41': + resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@13.0.5': + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -125,12 +448,152 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@30.0.0': + resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} + '@types/node@22.15.31': resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@11.0.5': + resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + '@vladfrangu/async_event_emitter@2.4.6': resolution: {integrity: sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -144,6 +607,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -156,6 +623,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -167,9 +638,45 @@ packages: arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + babel-jest@30.1.2: + resolution: {integrity: sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + + babel-plugin-istanbul@7.0.1: + resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} + engines: {node: '>=12'} + + babel-plugin-jest-hoist@30.0.1: + resolution: {integrity: sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@30.0.1: + resolution: {integrity: sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + baseline-browser-mapping@2.8.5: + resolution: {integrity: sha512-TiU4qUT9jdCuh4aVOG7H1QozyeI2sZRqoRPdqBIaslfNt4WUSanRBueAwl2x5jt4rXBMim3lIN2x6yT8PDi24Q==} + hasBin: true + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -177,14 +684,80 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.26.2: + resolution: {integrity: sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + bson@6.10.4: + resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} + engines: {node: '>=16.20.1'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + cacheables@2.0.0: + resolution: {integrity: sha512-PLxJ2og8n0mNzXiEQ8ythJI6r/IP7hiOUZz7Cqhb+IoPuVHu5st+IvagQVzT8aEggDvHp3dhoHOLypNy8c+dbA==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001743: + resolution: {integrity: sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + ci-info@4.3.0: + resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} + engines: {node: '>=8'} + + cjs-module-lexer@2.1.0: + resolution: {integrity: sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -195,6 +768,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -211,6 +787,22 @@ packages: supports-color: optional: true + dedent@1.7.0: + resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -228,47 +820,165 @@ packages: peerDependencies: discord.js: '>=14 || ^14.0.0-dev' + dotenv@17.1.0: + resolution: {integrity: sha512-tG9VUTJTuju6GcXgbdsOuRhupE8cb4mRgY5JLRCh4MtGoVo3/gfGUtOMwmProM6d0ba2mCFvv+WrpYJV6qgJXQ==} + engines: {node: '>=12'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.221: + resolution: {integrity: sha512-/1hFJ39wkW01ogqSyYoA4goOXOtMRy6B+yvA1u42nnsEGtHzIzmk93aPISumVQeblj47JUHLC9coCjUxb1EvtQ==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit-x@0.2.2: + resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} + engines: {node: '>= 0.8.0'} + + expect@30.1.2: + resolution: {integrity: sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + glob@11.0.3: resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} hasBin: true + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -281,6 +991,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -289,79 +1003,486 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@4.1.1: - resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} - engines: {node: 20 || >=22} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} - lodash.snakecase@4.1.1: - resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} - lru-cache@11.1.0: - resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} - engines: {node: 20 || >=22} + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} - magic-bytes.js@1.12.1: - resolution: {integrity: sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==} + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} + jest-changed-files@30.0.5: + resolution: {integrity: sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + jest-circus@30.1.3: + resolution: {integrity: sha512-Yf3dnhRON2GJT4RYzM89t/EXIWNxKTpWTL9BfF3+geFetWP4XSvJjiU1vrWplOiUkmq8cHLiwuhz+XuUp9DscA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - nodemon@3.1.10: - resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} - engines: {node: '>=10'} + jest-cli@30.1.3: + resolution: {integrity: sha512-G8E2Ol3OKch1DEeIBl41NP7OiC6LBhfg25Btv+idcusmoUSpqUkbrneMqbW9lVpI/rCKb/uETidb7DNteheuAQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.1.3: + resolution: {integrity: sha512-M/f7gqdQEPgZNA181Myz+GXCe8jXcJsGjCMXUzRj22FIXsZOyHNte84e0exntOvdPaeh9tA0w+B8qlP2fAezfw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@types/node': '*' + esbuild-register: '>=3.4.0' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@30.1.2: + resolution: {integrity: sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-docblock@30.0.1: + resolution: {integrity: sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-each@30.1.0: + resolution: {integrity: sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-environment-node@30.1.2: + resolution: {integrity: sha512-w8qBiXtqGWJ9xpJIA98M0EIoq079GOQRQUyse5qg1plShUCQ0Ek1VTTcczqKrn3f24TFAgFtT+4q3aOXvjbsuA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-haste-map@30.1.0: + resolution: {integrity: sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-leak-detector@30.1.0: + resolution: {integrity: sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.1.2: + resolution: {integrity: sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.1.0: + resolution: {integrity: sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.0.5: + resolution: {integrity: sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve-dependencies@30.1.3: + resolution: {integrity: sha512-DNfq3WGmuRyHRHfEet+Zm3QOmVFtIarUOQHHryKPc0YL9ROfgWZxl4+aZq/VAzok2SS3gZdniP+dO4zgo59hBg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve@30.1.3: + resolution: {integrity: sha512-DI4PtTqzw9GwELFS41sdMK32Ajp3XZQ8iygeDMWkxlRhm7uUTOFSZFVZABFuxr0jvspn8MAYy54NxZCsuCTSOw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runner@30.1.3: + resolution: {integrity: sha512-dd1ORcxQraW44Uz029TtXj85W11yvLpDuIzNOlofrC8GN+SgDlgY4BvyxJiVeuabA1t6idjNbX59jLd2oplOGQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runtime@30.1.3: + resolution: {integrity: sha512-WS8xgjuNSphdIGnleQcJ3AKE4tBKOVP+tKhCD0u+Tb2sBmsU8DxfbBpZX7//+XOz81zVs4eFpJQwBNji2Y07DA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-snapshot@30.1.2: + resolution: {integrity: sha512-4q4+6+1c8B6Cy5pGgFvjDy/Pa6VYRiGu0yQafKkJ9u6wQx4G5PqI2QR6nxTl43yy7IWsINwz6oT4o6tD12a8Dg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.0.5: + resolution: {integrity: sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@30.1.0: + resolution: {integrity: sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-watcher@30.1.3: + resolution: {integrity: sha512-6jQUZCP1BTL2gvG9E4YF06Ytq4yMb4If6YoQGRR6PpjtqOXSP3sKe2kqwB6SQ+H9DezOfZaSLnmka1NtGm3fCQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-worker@30.1.0: + resolution: {integrity: sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest@30.1.3: + resolution: {integrity: sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + kareem@2.6.3: + resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==} + engines: {node: '>=12.0.0'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-bytes.js@1.12.1: + resolution: {integrity: sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mongodb-connection-string-url@3.0.2: + resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==} + + mongodb@6.16.0: + resolution: {integrity: sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==} + engines: {node: '>=16.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.188.0 + '@mongodb-js/zstd': ^1.1.0 || ^2.0.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: '>=6.0.0 <7' + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + + mongoose-long@0.8.0: + resolution: {integrity: sha512-gNpLDs9HE5W5rKuZPFOzFB1Ei/SJbSSA6HlF/wR5qGU3N9K5SXfXlvWm+uUWpqZ13H3Zhna3Ww6ILGW4TjFP+g==} + peerDependencies: + mongoose: 4.x || 5.x || 6.x || 7.x || 8.x + + mongoose@8.15.1: + resolution: {integrity: sha512-RhQ4DzmBi5BNGcS0w4u1vdMRIKcteXTCNzDt1j7XRcdWYBz1MjMjulBhPaeC5jBCHOD1yinuOFTTSOWLLGexWw==} + engines: {node: '>=16.20.1'} + + mpath@0.9.0: + resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} + engines: {node: '>=4.0.0'} + + mquery@5.0.0: + resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==} + engines: {node: '>=14.0.0'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + napi-postinstall@0.3.3: + resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.21: + resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} + + nodemon@3.1.10: + resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} + engines: {node: '>=10'} hasBin: true normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.7.0: + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} + hasBin: true + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + prettier@3.5.3: resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} engines: {node: '>=14'} hasBin: true + pretty-format@30.0.5: + resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@7.0.1: + resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -375,6 +1496,12 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + sift@17.1.3: + resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -383,6 +1510,38 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -399,10 +1558,44 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -411,6 +1604,37 @@ packages: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + ts-jest@29.4.2: + resolution: {integrity: sha512-pBNOkn4HtuLpNrXTMVRC9b642CBaDnKqWXny4OzuoULT9S7Kf8MMlaRe2veKax12rjf5WcpMBhVPbQurlWGNxA==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} @@ -438,6 +1662,18 @@ packages: resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} engines: {node: '>= 6.0.0'} + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typedi@0.10.0: resolution: {integrity: sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==} @@ -446,6 +1682,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} @@ -456,14 +1697,41 @@ packages: resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==} engines: {node: '>=18.17'} + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -472,6 +1740,13 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ws@8.18.2: resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} engines: {node: '>=10.0.0'} @@ -484,20 +1759,228 @@ packages: utf-8-validate: optional: true + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + snapshots: - '@cspotcode/source-map-support@0.8.1': + '@babel/code-frame@7.27.1': dependencies: - '@jridgewell/trace-mapping': 0.3.9 + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@discordjs/builders@1.11.2': + '@babel/compat-data@7.28.4': {} + + '@babel/core@7.28.4': dependencies: - '@discordjs/formatters': 0.6.1 - '@discordjs/util': 1.1.1 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.1(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.4 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.26.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + + '@babel/traverse@7.28.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + debug: 4.4.1(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@bcoe/v8-coverage@0.2.3': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@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.11 fast-deep-equal: 3.1.3 @@ -552,6 +2035,22 @@ snapshots: '@discordx/internal@1.1.5': {} + '@emnapi/core@1.5.0': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.0': @@ -567,15 +2066,235 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@30.1.2': + dependencies: + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + chalk: 4.1.2 + jest-message-util: 30.1.0 + jest-util: 30.0.5 + slash: 3.0.0 + + '@jest/core@30.1.3(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3))': + dependencies: + '@jest/console': 30.1.2 + '@jest/pattern': 30.0.1 + '@jest/reporters': 30.1.3 + '@jest/test-result': 30.1.3 + '@jest/transform': 30.1.2 + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.3.0 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-changed-files: 30.0.5 + jest-config: 30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)) + jest-haste-map: 30.1.0 + jest-message-util: 30.1.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.1.3 + jest-resolve-dependencies: 30.1.3 + jest-runner: 30.1.3 + jest-runtime: 30.1.3 + jest-snapshot: 30.1.2 + jest-util: 30.0.5 + jest-validate: 30.1.0 + jest-watcher: 30.1.3 + micromatch: 4.0.8 + pretty-format: 30.0.5 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + '@jest/diff-sequences@30.0.1': {} + + '@jest/environment@30.1.2': + dependencies: + '@jest/fake-timers': 30.1.2 + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + jest-mock: 30.0.5 + + '@jest/expect-utils@30.1.2': + dependencies: + '@jest/get-type': 30.1.0 + + '@jest/expect@30.1.2': + dependencies: + expect: 30.1.2 + jest-snapshot: 30.1.2 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@30.1.2': + dependencies: + '@jest/types': 30.0.5 + '@sinonjs/fake-timers': 13.0.5 + '@types/node': 22.15.31 + jest-message-util: 30.1.0 + jest-mock: 30.0.5 + jest-util: 30.0.5 + + '@jest/get-type@30.1.0': {} + + '@jest/globals@30.1.2': + dependencies: + '@jest/environment': 30.1.2 + '@jest/expect': 30.1.2 + '@jest/types': 30.0.5 + jest-mock: 30.0.5 + transitivePeerDependencies: + - supports-color + + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 22.15.31 + jest-regex-util: 30.0.1 + + '@jest/reporters@30.1.3': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 30.1.2 + '@jest/test-result': 30.1.3 + '@jest/transform': 30.1.2 + '@jest/types': 30.0.5 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 22.15.31 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit-x: 0.2.2 + glob: 10.4.5 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + jest-message-util: 30.1.0 + jest-util: 30.0.5 + jest-worker: 30.1.0 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@30.0.5': + dependencies: + '@sinclair/typebox': 0.34.41 + + '@jest/snapshot-utils@30.1.2': + dependencies: + '@jest/types': 30.0.5 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + '@jest/source-map@30.0.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@30.1.3': + dependencies: + '@jest/console': 30.1.2 + '@jest/types': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@30.1.3': + dependencies: + '@jest/test-result': 30.1.3 + graceful-fs: 4.2.11 + jest-haste-map: 30.1.0 + slash: 3.0.0 + + '@jest/transform@30.1.2': + dependencies: + '@babel/core': 7.28.4 + '@jest/types': 30.0.5 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 7.0.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.1.0 + jest-regex-util: 30.0.1 + jest-util: 30.0.5 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + + '@jest/types@30.0.5': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.15.31 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@mongodb-js/saslprep@1.3.0': + dependencies: + sparse-bitfield: 3.0.3 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.5.0 + '@emnapi/runtime': 1.5.0 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + '@sapphire/async-queue@1.5.5': {} '@sapphire/shapeshift@4.0.0': @@ -585,6 +2304,16 @@ snapshots: '@sapphire/snowflake@3.5.3': {} + '@sinclair/typebox@0.34.41': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@13.0.5': + dependencies: + '@sinonjs/commons': 3.0.1 + '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} @@ -593,14 +2322,130 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.4 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.4 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@30.0.0': + dependencies: + expect: 30.1.2 + pretty-format: 30.0.5 + '@types/node@22.15.31': dependencies: undici-types: 6.21.0 + '@types/stack-utils@2.0.3': {} + + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@11.0.5': + dependencies: + '@types/webidl-conversions': 7.0.3 + '@types/ws@8.18.1': dependencies: '@types/node': 22.15.31 + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@ungap/structured-clone@1.3.0': {} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + '@vladfrangu/async_event_emitter@2.4.6': {} acorn-walk@8.3.4: @@ -609,6 +2454,10 @@ snapshots: acorn@8.15.0: {} + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -617,6 +2466,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} anymatch@3.1.3: @@ -626,150 +2477,791 @@ snapshots: arg@4.1.3: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + atomic-sleep@1.0.0: {} + + babel-jest@30.1.2(@babel/core@7.28.4): + dependencies: + '@babel/core': 7.28.4 + '@jest/transform': 30.1.2 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 7.0.1 + babel-preset-jest: 30.0.1(@babel/core@7.28.4) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@7.0.1: + dependencies: + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@30.0.1: + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + '@types/babel__core': 7.20.5 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.4): + dependencies: + '@babel/core': 7.28.4 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.4) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.4) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.4) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.4) + + babel-preset-jest@30.0.1(@babel/core@7.28.4): + dependencies: + '@babel/core': 7.28.4 + babel-plugin-jest-hoist: 30.0.1 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.4) + balanced-match@1.0.2: {} + baseline-browser-mapping@2.8.5: {} + binary-extensions@2.3.0: {} brace-expansion@1.1.12: dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.26.2: + dependencies: + baseline-browser-mapping: 2.8.5 + caniuse-lite: 1.0.30001743 + electron-to-chromium: 1.5.221 + node-releases: 2.0.21 + update-browserslist-db: 1.1.3(browserslist@4.26.2) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + bson@6.10.4: {} + + buffer-from@1.1.2: {} + + cacheables@2.0.0: {} + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001743: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + ci-info@4.3.0: {} + + cjs-module-lexer@2.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.1(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + dedent@1.7.0: {} + + deepmerge@4.3.1: {} + + detect-newline@3.1.0: {} + + diff@4.0.2: {} + + discord-api-types@0.38.11: {} + + discord.js@14.19.3: + dependencies: + '@discordjs/builders': 1.11.2 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.6.1 + '@discordjs/rest': 2.5.0 + '@discordjs/util': 1.1.1 + '@discordjs/ws': 1.2.2 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.38.11 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + magic-bytes.js: 1.12.1 + tslib: 2.8.1 + undici: 6.21.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + discordx@11.12.4(discord.js@14.19.3): + dependencies: + '@discordx/di': 3.3.4 + '@discordx/internal': 1.1.5 + discord.js: 14.19.3 + lodash: 4.17.21 + + dotenv@17.1.0: {} + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.221: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + + esprima@4.0.1: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit-x@0.2.2: {} + + expect@30.1.2: + dependencies: + '@jest/expect-utils': 30.1.2 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 + jest-mock: 30.0.5 + jest-util: 30.0.5 + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-redact@3.5.0: {} + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-package-type@0.1.0: {} + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + graceful-fs@4.2.11: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + html-escaper@2.0.2: {} + + human-signals@2.1.0: {} + + ignore-by-default@1.0.1: {} + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-stream@2.0.1: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.28.4 + '@babel/parser': 7.28.4 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.1(supports-color@5.5.0) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + jest-changed-files@30.0.5: + dependencies: + execa: 5.1.1 + jest-util: 30.0.5 + p-limit: 3.1.0 + + jest-circus@30.1.3: + dependencies: + '@jest/environment': 30.1.2 + '@jest/expect': 30.1.2 + '@jest/test-result': 30.1.3 + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.0 + is-generator-fn: 2.1.0 + jest-each: 30.1.0 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 + jest-runtime: 30.1.3 + jest-snapshot: 30.1.2 + jest-util: 30.0.5 + p-limit: 3.1.0 + pretty-format: 30.0.5 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)): + dependencies: + '@jest/core': 30.1.3(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)) + '@jest/test-result': 30.1.3 + '@jest/types': 30.0.5 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)) + jest-util: 30.0.5 + jest-validate: 30.1.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)): + dependencies: + '@babel/core': 7.28.4 + '@jest/get-type': 30.1.0 + '@jest/pattern': 30.0.1 + '@jest/test-sequencer': 30.1.3 + '@jest/types': 30.0.5 + babel-jest: 30.1.2(@babel/core@7.28.4) + chalk: 4.1.2 + ci-info: 4.3.0 + deepmerge: 4.3.1 + glob: 10.4.5 + graceful-fs: 4.2.11 + jest-circus: 30.1.3 + jest-docblock: 30.0.1 + jest-environment-node: 30.1.2 + jest-regex-util: 30.0.1 + jest-resolve: 30.1.3 + jest-runner: 30.1.3 + jest-util: 30.0.5 + jest-validate: 30.1.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 30.0.5 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.15.31 + ts-node: 10.9.2(@types/node@22.15.31)(typescript@5.7.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@30.1.2: + dependencies: + '@jest/diff-sequences': 30.0.1 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.0.5 + + jest-docblock@30.0.1: + dependencies: + detect-newline: 3.1.0 + + jest-each@30.1.0: + dependencies: + '@jest/get-type': 30.1.0 + '@jest/types': 30.0.5 + chalk: 4.1.2 + jest-util: 30.0.5 + pretty-format: 30.0.5 - braces@3.0.3: + jest-environment-node@30.1.2: dependencies: - fill-range: 7.1.1 + '@jest/environment': 30.1.2 + '@jest/fake-timers': 30.1.2 + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + jest-mock: 30.0.5 + jest-util: 30.0.5 + jest-validate: 30.1.0 - chokidar@3.6.0: + jest-haste-map@30.1.0: dependencies: + '@jest/types': 30.0.5 + '@types/node': 22.15.31 anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.0.1 + jest-util: 30.0.5 + jest-worker: 30.1.0 + micromatch: 4.0.8 + walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 - color-convert@2.0.1: + jest-leak-detector@30.1.0: dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - concat-map@0.0.1: {} - - create-require@1.1.1: {} + '@jest/get-type': 30.1.0 + pretty-format: 30.0.5 - cross-spawn@7.0.6: + jest-matcher-utils@30.1.2: dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.1.2 + pretty-format: 30.0.5 - debug@4.4.1(supports-color@5.5.0): + jest-message-util@30.1.0: dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 5.5.0 + '@babel/code-frame': 7.27.1 + '@jest/types': 30.0.5 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 30.0.5 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.0.5: + dependencies: + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + jest-util: 30.0.5 - diff@4.0.2: {} + jest-pnp-resolver@1.2.3(jest-resolve@30.1.3): + optionalDependencies: + jest-resolve: 30.1.3 - discord-api-types@0.38.11: {} + jest-regex-util@30.0.1: {} - discord.js@14.19.3: + jest-resolve-dependencies@30.1.3: dependencies: - '@discordjs/builders': 1.11.2 - '@discordjs/collection': 1.5.3 - '@discordjs/formatters': 0.6.1 - '@discordjs/rest': 2.5.0 - '@discordjs/util': 1.1.1 - '@discordjs/ws': 1.2.2 - '@sapphire/snowflake': 3.5.3 - discord-api-types: 0.38.11 - fast-deep-equal: 3.1.3 - lodash.snakecase: 4.1.1 - magic-bytes.js: 1.12.1 - tslib: 2.8.1 - undici: 6.21.1 + jest-regex-util: 30.0.1 + jest-snapshot: 30.1.2 transitivePeerDependencies: - - bufferutil - - utf-8-validate + - supports-color - discordx@11.12.4(discord.js@14.19.3): + jest-resolve@30.1.3: dependencies: - '@discordx/di': 3.3.4 - '@discordx/internal': 1.1.5 - discord.js: 14.19.3 - lodash: 4.17.21 - - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - fast-deep-equal@3.1.3: {} + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.1.0 + jest-pnp-resolver: 1.2.3(jest-resolve@30.1.3) + jest-util: 30.0.5 + jest-validate: 30.1.0 + slash: 3.0.0 + unrs-resolver: 1.11.1 + + jest-runner@30.1.3: + dependencies: + '@jest/console': 30.1.2 + '@jest/environment': 30.1.2 + '@jest/test-result': 30.1.3 + '@jest/transform': 30.1.2 + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.0.1 + jest-environment-node: 30.1.2 + jest-haste-map: 30.1.0 + jest-leak-detector: 30.1.0 + jest-message-util: 30.1.0 + jest-resolve: 30.1.3 + jest-runtime: 30.1.3 + jest-util: 30.0.5 + jest-watcher: 30.1.3 + jest-worker: 30.1.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color - fill-range@7.1.1: + jest-runtime@30.1.3: dependencies: - to-regex-range: 5.0.1 + '@jest/environment': 30.1.2 + '@jest/fake-timers': 30.1.2 + '@jest/globals': 30.1.2 + '@jest/source-map': 30.0.1 + '@jest/test-result': 30.1.3 + '@jest/transform': 30.1.2 + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + chalk: 4.1.2 + cjs-module-lexer: 2.1.0 + collect-v8-coverage: 1.0.2 + glob: 10.4.5 + graceful-fs: 4.2.11 + jest-haste-map: 30.1.0 + jest-message-util: 30.1.0 + jest-mock: 30.0.5 + jest-regex-util: 30.0.1 + jest-resolve: 30.1.3 + jest-snapshot: 30.1.2 + jest-util: 30.0.5 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color - foreground-child@3.3.1: + jest-snapshot@30.1.2: dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 + '@babel/core': 7.28.4 + '@babel/generator': 7.28.3 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) + '@babel/types': 7.28.4 + '@jest/expect-utils': 30.1.2 + '@jest/get-type': 30.1.0 + '@jest/snapshot-utils': 30.1.2 + '@jest/transform': 30.1.2 + '@jest/types': 30.0.5 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.4) + chalk: 4.1.2 + expect: 30.1.2 + graceful-fs: 4.2.11 + jest-diff: 30.1.2 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 + jest-util: 30.0.5 + pretty-format: 30.0.5 + semver: 7.7.2 + synckit: 0.11.11 + transitivePeerDependencies: + - supports-color - fsevents@2.3.3: - optional: true + jest-util@30.0.5: + dependencies: + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + chalk: 4.1.2 + ci-info: 4.3.0 + graceful-fs: 4.2.11 + picomatch: 4.0.3 - glob-parent@5.1.2: + jest-validate@30.1.0: dependencies: - is-glob: 4.0.3 + '@jest/get-type': 30.1.0 + '@jest/types': 30.0.5 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.0.5 + + jest-watcher@30.1.3: + dependencies: + '@jest/test-result': 30.1.3 + '@jest/types': 30.0.5 + '@types/node': 22.15.31 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.0.5 + string-length: 4.0.2 - glob@11.0.3: + jest-worker@30.1.0: dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.0.3 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.0 + '@types/node': 22.15.31 + '@ungap/structured-clone': 1.3.0 + jest-util: 30.0.5 + merge-stream: 2.0.0 + supports-color: 8.1.1 - has-flag@3.0.0: {} + jest@30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)): + dependencies: + '@jest/core': 30.1.3(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)) + '@jest/types': 30.0.5 + import-local: 3.2.0 + jest-cli: 30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node - ignore-by-default@1.0.1: {} + js-tokens@4.0.0: {} - is-binary-path@2.1.0: + js-yaml@3.14.1: dependencies: - binary-extensions: 2.3.0 + argparse: 1.0.10 + esprima: 4.0.1 - is-extglob@2.1.1: {} + jsesc@3.1.0: {} - is-fullwidth-code-point@3.0.0: {} + json-parse-even-better-errors@2.3.1: {} - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 + json5@2.2.3: {} - is-number@7.0.0: {} + kareem@2.6.3: {} - isexe@2.0.0: {} + leven@3.1.0: {} - jackspeak@4.1.1: + lines-and-columns@1.2.4: {} + + locate-path@5.0.0: dependencies: - '@isaacs/cliui': 8.0.2 + p-locate: 4.1.0 + + lodash.memoize@4.1.2: {} lodash.snakecase@4.1.1: {} lodash@4.17.21: {} + lru-cache@10.4.3: {} + lru-cache@11.1.0: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + magic-bytes.js@1.12.1: {} + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + make-error@1.3.6: {} + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + memory-pager@1.5.0: {} + + merge-stream@2.0.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@2.1.0: {} + minimatch@10.0.3: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -778,10 +3270,68 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + minipass@7.1.2: {} + mongodb-connection-string-url@3.0.2: + dependencies: + '@types/whatwg-url': 11.0.5 + whatwg-url: 14.2.0 + + mongodb@6.16.0: + dependencies: + '@mongodb-js/saslprep': 1.3.0 + bson: 6.10.4 + mongodb-connection-string-url: 3.0.2 + + mongoose-long@0.8.0(mongoose@8.15.1): + dependencies: + mongoose: 8.15.1 + + mongoose@8.15.1: + dependencies: + bson: 6.10.4 + kareem: 2.6.3 + mongodb: 6.16.0 + mpath: 0.9.0 + mquery: 5.0.0 + ms: 2.1.3 + sift: 17.1.3 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + + mpath@0.9.0: {} + + mquery@5.0.0: + dependencies: + debug: 4.4.1(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + ms@2.1.3: {} + napi-postinstall@0.3.3: {} + + natural-compare@1.4.0: {} + + neo-async@2.6.2: {} + + node-int64@0.4.0: {} + + node-releases@2.0.21: {} + nodemon@3.1.10: dependencies: chokidar: 3.6.0 @@ -797,25 +3347,129 @@ snapshots: normalize-path@3.0.0: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-scurry@2.0.0: dependencies: lru-cache: 11.1.0 minipass: 7.1.2 + picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.3: {} + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.7.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + prettier@3.5.3: {} + pretty-format@30.0.5: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + process-warning@5.0.0: {} + pstree.remy@1.1.8: {} + punycode@2.3.1: {} + + pure-rand@7.0.1: {} + + quick-format-unescaped@4.0.4: {} + + react-is@18.3.1: {} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 + real-require@0.2.0: {} + + require-directory@2.1.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@5.0.0: {} + + safe-stable-stringify@2.5.0: {} + + semver@6.3.1: {} + semver@7.7.2: {} shebang-command@2.0.0: @@ -824,12 +3478,46 @@ snapshots: shebang-regex@3.0.0: {} + sift@17.1.3: {} + + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} simple-update-notifier@2.0.0: dependencies: semver: 7.7.2 + slash@3.0.0: {} + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -850,16 +3538,70 @@ snapshots: dependencies: ansi-regex: 6.1.0 + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + tmpl@1.0.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 touch@3.1.1: {} + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + ts-jest@29.4.2(@babel/core@7.28.4)(@jest/transform@30.1.2)(@jest/types@30.0.5)(babel-jest@30.1.2(@babel/core@7.28.4))(jest-util@30.0.5)(jest@30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)))(typescript@5.7.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 30.1.3(@types/node@22.15.31)(ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.7.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.28.4 + '@jest/transform': 30.1.2 + '@jest/types': 30.0.5 + babel-jest: 30.1.2(@babel/core@7.28.4) + jest-util: 30.0.5 + ts-mixer@6.0.4: {} ts-node@10.9.2(@types/node@22.15.31)(typescript@5.7.3): @@ -888,22 +3630,80 @@ snapshots: dependencies: tslib: 1.14.1 + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + typedi@0.10.0: {} typescript@5.7.3: {} + uglify-js@3.19.3: + optional: true + undefsafe@2.0.5: {} undici-types@6.21.0: {} undici@6.21.1: {} + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.3 + 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 + + update-browserslist-db@1.1.3(browserslist@4.26.2): + dependencies: + browserslist: 4.26.2 + escalade: 3.2.0 + picocolors: 1.1.1 + v8-compile-cache-lib@3.0.1: {} + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 + wordwrap@1.0.0: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -916,6 +3716,31 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrappy@1.0.2: {} + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + ws@8.18.2: {} + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/src/commands/slashes.ts b/src/commands/slashes.ts deleted file mode 100644 index 61bfa32..0000000 --- a/src/commands/slashes.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { CommandInteraction } from "discord.js"; -import { Discord, Slash } from "discordx"; - -@Discord() -export class Example { - @Slash({ description: "ping" }) - async ping(interaction: CommandInteraction): Promise { - await interaction.reply("pong!"); - } -} diff --git a/src/events/common.ts b/src/events/common.ts deleted file mode 100644 index 3a4e946..0000000 --- a/src/events/common.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ArgsOf, Client } from "discordx"; -import { Discord, On } from "discordx"; - -@Discord() -export class Example { - @On() - messageDelete([message]: ArgsOf<"messageDelete">, client: Client): void { - console.log("Message Deleted", client.user?.username, message.content); - } -} diff --git a/src/lib/arc3.ts b/src/lib/arc3.ts new file mode 100644 index 0000000..103342b --- /dev/null +++ b/src/lib/arc3.ts @@ -0,0 +1,195 @@ +import { dirname, importx } from "@discordx/importer"; +import type { Guild, GuildMember, Interaction, Message } from "discord.js"; +import { GatewayDispatchEvents, IntentsBitField, Partials } from "discord.js"; +import { Client } from "discordx"; +import mongoose from "mongoose"; +import { Logger } from "./logger/index.js"; +import { default as GuildInfo } from "./schema/v1/Guild.js"; +import mongooseLong from 'mongoose-long' + +mongooseLong(mongoose); +const { Types: { Long, ObjectId} } = mongoose; + + +export namespace Arc3 { + + export class Arc3 { + + public static clientInstance : Client; + private readonly clientInstance : Client; + public static readonly clientLogger = new Logger("SPARK", 'debug'); + public static readonly clientVersion = "0.0.1"; + + /** + * Creates a new instance of the Arc3 class. + * This class is responsible for initializing the Discord client and handling interactions. + */ + constructor() { + + Arc3.clientInstance = new Client({ + + // To use only guild command + // botGuilds: [(client) => client.guilds.cache.map((guild) => guild.id)], + + partials: [Partials.Channel, Partials.Message], + + // Discord intents + intents: [ + IntentsBitField.Flags.Guilds, + IntentsBitField.Flags.GuildMessages, + IntentsBitField.Flags.GuildMembers, + IntentsBitField.Flags.DirectMessages, + IntentsBitField.Flags.MessageContent, + IntentsBitField.Flags.GuildMessageReactions, + IntentsBitField.Flags.GuildPresences, + IntentsBitField.Flags.GuildInvites, + IntentsBitField.Flags.GuildModeration, + IntentsBitField.Flags.GuildScheduledEvents, + IntentsBitField.Flags.GuildWebhooks, + IntentsBitField.Flags.GuildIntegrations, + IntentsBitField.Flags.GuildExpressions, + IntentsBitField.Flags.GuildMessageTyping, + IntentsBitField.Flags.DirectMessageReactions, + IntentsBitField.Flags.DirectMessageTyping, + IntentsBitField.Flags.GuildVoiceStates, + ], + + // Debug logs are disabled in silent mode + silent: false, + + logger: Arc3.clientLogger, + + }); + + this.clientInstance = Arc3.clientInstance; + + this.clientInstance.on('interactionCreate', this.onInteractionCreate); + this.clientInstance.on('ready', this.onReady); + this.clientInstance.on('guildMemberAdd', this.onGuildMemberAdd) + + } + + /** + * Handles the guild member add event. + * This method is called when a new member joins a guild. + * @param member The guild member that was added. + * @returns {void} + */ + private onGuildMemberAdd(member: GuildMember) { + + if ( !member || !member.user ) + return; + + if ( member.user.id !== this.clientInstance.user?.id ) + return; + + Arc3.clientLogger.info("Bot joined a new guild: " + member.guild.name); + + Arc3.NewGuild([], member.guild) + .then( () => Arc3.clientLogger.info("New guild created in database: " + member.guild.name) ) + .catch( e => Arc3.clientLogger.error("Error creating new guild in database: ", e) ); + + } + + /** + * Handles interaction creation events. + * @param interaction The interaction that was created. + */ + private onInteractionCreate(this: Client, interaction: Interaction) { + this.executeInteraction(interaction); + } + + /** + * Handles the ready event of the client. + * This method is called when the client is ready and connected to Discord. + * @this {Client} The Discord client instance. + * @return {Promise} A promise that resolves when the client is ready. + */ + private async onReady(this: Client) { + + const oAuth2Guilds = await this.guilds.fetch(); + const guilds = await Promise.all(oAuth2Guilds.map((x) => x.fetch())); + + if ( guilds.length <= 0 ) { + Arc3.clientLogger.info("No Guilds found!"); + return; + } + + await this.initApplicationCommands(); + + mongoose + .connect(process.env.MONGODB_URI?? "none") + .then( _ => { + Arc3.clientLogger.info("Connected to database!", process.env.MONGODB_URI); + }) + .catch( e => Arc3.clientLogger.error("Error connecting to MongoDB") ); + + guilds.forEach( async ( guild ) => { + + const cachedGuilds = await GuildInfo.find(); + + if ( cachedGuilds.map( x => x.guildsnowflake?.toString() ).includes(guild.id) ) + return; + + await Arc3.NewGuild(cachedGuilds, guild); + + }); + + // Log the bot's information + Arc3.clientLogger.info(`Logged in as ${this.user?.username}`); + Arc3.clientLogger.info(`Shard IDs: ${this.shard?.ids}`); + Arc3.clientLogger.info(`Recommended Shards: ${Math.round(guilds.length / 25000 )}`); + Arc3.clientLogger.info(`Registered ${this.applicationCommands.length} application Commands`); + Arc3.clientLogger.info(`Client is a member of ${guilds.length} guilds`); + + } + + /** + * Creates a new guild entry in the database. + * @param guildInfos The list of existing guild information. + * @param guild The guild to create an entry for. + * @returns A promise that resolves when the guild entry is created. + */ + public static async NewGuild(guildInfos: any[], guild: Guild) { + + if ( guildInfos.map( x => x.guildsnowflake?.toString()).includes(guild.id) ) + return; + + const newGuildInfo = new GuildInfo({ + _id: ObjectId.createFromTime(new Date().getTime()), + guildsnowflake: guild.id, + premium: false, + moderators: [], + ownerid: guild.ownerId + }); + + await newGuildInfo.save(); + + // TODO: Welcome Message!! + + } + + /** + * Runs the Arc3 bot. + * This method initializes the bot, loads commands and events, and starts the bot. + * @returns A promise that resolves when the bot is running. + */ + public async runAsync() { + + // The following syntax should be used in the ECMAScript environment + await importx(`${dirname(import.meta.url)}/discord/{events,commands}/**/*.{ts,js}`); + + // Let's start the bot + if (!process.env.TOKEN) { + throw Error("Could not find TOKEN in your environment"); + } + + // Log in with your bot token + await this.clientInstance.login(process.env.TOKEN); + + } + + } + +} + diff --git a/src/lib/discord/autocomplete/setConfigKeyAutoComplete.ts b/src/lib/discord/autocomplete/setConfigKeyAutoComplete.ts new file mode 100644 index 0000000..f713697 --- /dev/null +++ b/src/lib/discord/autocomplete/setConfigKeyAutoComplete.ts @@ -0,0 +1,22 @@ +import { AutocompleteInteraction } from "discord.js"; +import { GuildConfigKey } from "../../hooks/useGuildConfig.js"; + +export function SetConfigKeyAutoComplete(interaction: AutocompleteInteraction) { + + const values : string[] = []; + + for (let item in GuildConfigKey) { + values.push(item) + } + + // Respond with enum values + interaction.respond( + values.map(choice => ({ + name: choice, + value: choice, + })) + ); + +} + +export default SetConfigKeyAutoComplete; \ No newline at end of file diff --git a/src/lib/discord/commands/configs.ts b/src/lib/discord/commands/configs.ts new file mode 100644 index 0000000..e4d121d --- /dev/null +++ b/src/lib/discord/commands/configs.ts @@ -0,0 +1,50 @@ +import { ApplicationCommandOptionType, AutocompleteInteraction, InteractionCallback, SlashCommandStringOption, type CommandInteraction } from "discord.js"; +import { Discord, Guard, Slash, SlashOption } from "discordx"; +import { useGuildConfig } from "../../hooks/useGuildConfig.js"; +import { Blacklist } from "../guards/blacklist.js"; +import { Locale, useTextContent } from "../../hooks/useTextContent.js"; +import SetConfigKeyAutoComplete from "../autocomplete/setConfigKeyAutoComplete.js"; + +@Discord() +export class ConfigCommands { + + @Slash({ + name: "setconfig", + description: "Set config value" + }) + @Guard( + Blacklist + ) + async setConfig( + + @SlashOption({ + description: "Config key", + name: "key", + required: true, + type: ApplicationCommandOptionType.String, + autocomplete: SetConfigKeyAutoComplete + }) + key: string, + @SlashOption({ + description: "Config value", + name: "value", + required: true, + type: ApplicationCommandOptionType.String, + }) + value: string, + interaction: CommandInteraction) { + + await interaction.deferReply(); + + const { setConfig } = useGuildConfig().actions; + const { text } = useTextContent(Locale.EN).actions; + + await setConfig(interaction.guildId?? "0", key, value); + + await interaction.editReply({ + content: text('arc.command.setconfig.sucess'), + }); + + } + +} diff --git a/src/lib/discord/events/ModmailEvents.ts b/src/lib/discord/events/ModmailEvents.ts new file mode 100644 index 0000000..b396d6d --- /dev/null +++ b/src/lib/discord/events/ModmailEvents.ts @@ -0,0 +1,111 @@ +import { ArgsOf, ButtonComponent, Client, Discord, On, SelectMenuComponent } from "discordx"; +import { Arc3 } from "../../arc3.js"; +import { ButtonInteraction, StringSelectMenuInteraction } from "discord.js"; +import { Logger } from "pino"; + +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' +import { ModmailMessageService } from "../../service/ModmailMessageService.js"; +import { ModmailRepo } from "../../repositories/ModmailRepo.js"; +import { ModmailInteractionService } from "../../service/ModmailInteractionService.js"; + +mongooseLong(mongoose); + +const { Types: { Long, ObjectId} } = mongoose; + +@Discord() +export class ModmailEvents { + + private logger : Logger; + private modmailMessageService : ModmailMessageService; + private modmailInteractionService : ModmailInteractionService; + private modmailRepo: ModmailRepo; + + /** + * ModmailEvents class constructor + * Initializes the logger and modmail cache. + * @constructor + */ + constructor( + modmailRepo? : ModmailRepo, + modmailInteractionService? : ModmailInteractionService, + modmailMessageService? : ModmailMessageService + ) { + + this.logger = Arc3.Arc3.clientLogger.child("ModmailEvents"); + this.modmailRepo = modmailRepo || new ModmailRepo(); + this.modmailMessageService = modmailMessageService || new ModmailMessageService(this.modmailRepo); + this.modmailInteractionService = modmailInteractionService || new ModmailInteractionService(this.modmailRepo); + } + + /** + * Event handler for message creation. + * It processes modmail messages received in DMs. + * @param {ArgsOf<"messageCreate">} args - The arguments of the messageCreate event. + * @param {Client} client - The Discord client instance. + * @param {any} guardPayload - Additional payload for guards (not used here). + */ + @On({ event: "messageCreate" }) + onMessageCreate( + [message] : ArgsOf<"messageCreate">, + client: Client, + guardPayload: any, + ) { + + // Guard if the author is a bot + if (message.author.bot) + return; + + this.modmailMessageService.ProcessModmailMessageRecieved(message) + .catch( e => this.logger.error(e) ); + + } + + @On({ event: 'messageUpdate' }) + onMessageUpdate( + [oldMessage, newMessage] : ArgsOf<'messageUpdate'>, + client: Client, + guardPayload: any, + ) { + + // Guard if the author is a bot + if (newMessage.author?.bot) + return; + + this.modmailMessageService.ProcessModmailMessageUpdated(oldMessage, newMessage) + .catch( e => this.logger.error(e) ); + + } + + /** + * Event handler for interaction creation. + * It handles the modmail select menu interaction. + * @param {StringSelectMenuInteraction} interaction - The interaction object. + */ + @SelectMenuComponent({ id: "modmail.select.server"}) + async handleModmailSelectMenu(interaction: StringSelectMenuInteraction) { + + await interaction.deferReply() + + this.modmailInteractionService.ProcessModmailServerSelection(interaction) + .catch( e => this.logger.error(e) ); + } + + /** + * Event handler for modmail save button interaction. + * It processes the modmail save button interaction. + * @param {ButtonInteraction} interaction - The button interaction object. + */ + @ButtonComponent({ id: new RegExp("modmail\.save\..*") }) + async handleModmailSaveButton(interaction: ButtonInteraction) { + + await interaction.deferReply(); + + this.modmailInteractionService.ProcessModmailSaveButton(interaction) + .catch( e => this.logger.error(e) ); + + } + + +} + diff --git a/src/lib/discord/guards/blacklist.ts b/src/lib/discord/guards/blacklist.ts new file mode 100644 index 0000000..b5037a8 --- /dev/null +++ b/src/lib/discord/guards/blacklist.ts @@ -0,0 +1,36 @@ +import { CommandInteraction } from "discord.js"; +import { GuardFunction } from "discordx"; +import { useBlacklist } from "../../hooks/useBlacklist.js"; +import { Locale, useTextContent } from "../../hooks/useTextContent.js"; + +/** + * Blacklist guard to prevent blacklisted users from using certain commands + * @param interactionParams + * @param client + * @param next + */ +export const Blacklist: GuardFunction = async ( + interactionParams, + client, + next +) => { + + const { getBlacklist } = useBlacklist().actions; + const { text } = useTextContent(Locale.EN).actions; + + const blacklistCondition = await getBlacklist( + interactionParams.guildId?? "0", + interactionParams.user.id, + interactionParams.commandName + ); + + if (!blacklistCondition) { + next(); + return; + } + + await interactionParams.reply({ + content: text('arc.blacklist'), + }) + +} \ No newline at end of file diff --git a/src/lib/hooks/__tests__/useActiveModmails.test.ts b/src/lib/hooks/__tests__/useActiveModmails.test.ts new file mode 100644 index 0000000..9189b86 --- /dev/null +++ b/src/lib/hooks/__tests__/useActiveModmails.test.ts @@ -0,0 +1,122 @@ +import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; + +// Create mock instances that will be used by the module +const mockCacheInstance = { + cacheable: jest.fn() as jest.MockedFunction, + isCached: jest.fn() as jest.MockedFunction, + clear: jest.fn() as jest.MockedFunction, +}; + +const mockLogger = { + info: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + debug: jest.fn(), +}; + +const mockModmail = { + find: jest.fn() as jest.MockedFunction, +}; + +// Mock Cacheables +jest.mock('cacheables', () => ({ + Cacheables: jest.fn().mockImplementation(() => mockCacheInstance), +})); + +// Mock Arc3 +jest.mock('../../arc/arc3.js', () => ({ + Arc3: { + Arc3: { + clientLogger: { + child: jest.fn(() => mockLogger), + }, + }, + }, +})); + +// Mock Modmail schema +jest.mock('../../schema/v1/Modmail.js', () => ({ + default: mockModmail, +})); + +import { useActiveModmails } from '../useActiveModmails'; + +describe('useActiveModmails', () => { + beforeEach(() => { + // Reset all mocks + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize correctly and return actions and states', () => { + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useActiveModmails(); + + expect(result).toHaveProperty('actions'); + expect(result).toHaveProperty('states'); + expect(result.actions).toHaveProperty('buildCache'); + expect(result.states).toHaveProperty('activeModmailsCache'); + expect(result.states).toHaveProperty('logger'); + }); + + it('should return buildCache function that calls cacheable', async () => { + const mockModmails = [ + { _id: '1', userId: 'user1', channelId: 'channel1' }, + { _id: '2', userId: 'user2', channelId: 'channel2' }, + ]; + + (mockModmail.find as jest.MockedFunction).mockResolvedValue(mockModmails); + (mockCacheInstance.cacheable as jest.MockedFunction).mockResolvedValue(mockModmails); + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useActiveModmails(); + await result.actions.buildCache(); + + expect(mockCacheInstance.cacheable).toHaveBeenCalled(); + }); + + it('should fetch modmails from database when building cache', async () => { + const mockModmails = [ + { _id: '1', userId: 'user1', channelId: 'channel1' }, + { _id: '2', userId: 'user2', channelId: 'channel2' }, + ]; + + (mockModmail.find as jest.MockedFunction).mockResolvedValue(mockModmails); + (mockCacheInstance.cacheable as jest.MockedFunction).mockImplementation(async (fn: () => Promise) => await fn()); + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useActiveModmails(); + const cacheResult = await result.actions.buildCache(); + + expect(mockModmail.find).toHaveBeenCalledWith(); + expect(cacheResult).toEqual(mockModmails); + }); + + it('should handle database errors gracefully', async () => { + const mockError = new Error('Database connection failed'); + (mockModmail.find as jest.MockedFunction).mockRejectedValue(mockError); + (mockCacheInstance.cacheable as jest.MockedFunction).mockImplementation(async (fn: () => Promise) => await fn()); + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useActiveModmails(); + + await expect(result.actions.buildCache()).rejects.toThrow('Database connection failed'); + expect(mockModmail.find).toHaveBeenCalledWith(); + }); + + it('should return empty array when no modmails exist', async () => { + (mockModmail.find as jest.MockedFunction).mockResolvedValue([]); + (mockCacheInstance.cacheable as jest.MockedFunction).mockImplementation(async (fn: () => Promise) => await fn()); + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useActiveModmails(); + const cacheResult = await result.actions.buildCache(); + + expect(cacheResult).toEqual([]); + expect(mockModmail.find).toHaveBeenCalledWith(); + }); +}); \ No newline at end of file diff --git a/src/lib/hooks/__tests__/useBlacklist.test.ts b/src/lib/hooks/__tests__/useBlacklist.test.ts new file mode 100644 index 0000000..94b976c --- /dev/null +++ b/src/lib/hooks/__tests__/useBlacklist.test.ts @@ -0,0 +1,195 @@ +import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; + +// Mock Blacklist schema - declare mocks before jest.mock +const mockBlacklistSave = jest.fn() as jest.MockedFunction<() => Promise>; +const mockBlacklistDeleteOne = jest.fn() as jest.MockedFunction<(query: any) => Promise<{deletedCount: number}>>; +const mockBlacklistFind = jest.fn() as jest.MockedFunction<() => Promise>; + +// Create mock instances that will be used by the module +const mockCacheInstance = { + cacheable: jest.fn() as jest.MockedFunction, + isCached: jest.fn() as jest.MockedFunction, + delete: jest.fn() as jest.MockedFunction, +}; + +const mockLogger = { + info: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + debug: jest.fn(), +}; + +// Mock Cacheables +jest.mock('cacheables', () => ({ + Cacheables: jest.fn().mockImplementation(() => mockCacheInstance), +})); + +// Mock Arc3 +jest.mock('../../arc/arc3.js', () => ({ + Arc3: { + Arc3: { + clientLogger: { + child: jest.fn(() => mockLogger), + }, + }, + }, +})); + +// Mock mongoose and mongoose-long +jest.mock('mongoose', () => ({ + __esModule: true, + default: { + Types: { + Long: { + fromString: jest.fn((str) => ({ toString: () => str })), + }, + ObjectId: { + createFromTime: jest.fn((time) => ({ _id: `generated_${time}` })), + }, + }, + }, + Types: { + Long: { + fromString: jest.fn((str) => ({ toString: () => str })), + }, + ObjectId: { + createFromTime: jest.fn((time) => ({ _id: `generated_${time}` })), + }, + }, +})); + +jest.mock('mongoose-long', () => ({ + __esModule: true, + default: jest.fn().mockImplementation((mongoose) => mongoose), +})); + +jest.mock('../../schema/v1/Blacklist.js', () => { + const mockConstructor = jest.fn().mockImplementation((data: any) => ({ + ...(data || {}), + save: mockBlacklistSave, + })); + + // Add static methods to the constructor + Object.assign(mockConstructor, { + find: mockBlacklistFind, + deleteOne: mockBlacklistDeleteOne, + }); + + return { default: mockConstructor }; +}); + +import { useBlacklist } from '../useBlacklist'; + +describe('useBlacklist', () => { + beforeEach(() => { + // Reset all mocks + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize correctly and return actions and states', () => { + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useBlacklist(); + + expect(result).toHaveProperty('actions'); + expect(result).toHaveProperty('states'); + expect(result.actions).toHaveProperty('getBlacklist'); + expect(result.actions).toHaveProperty('setBlacklist'); + expect(result.actions).toHaveProperty('clearBlacklist'); + expect(result.states).toHaveProperty('blacklistCache'); + expect(result.states).toHaveProperty('logger'); + }); + + it('should build cache correctly from database records', async () => { + const mockBlacklists = [ + { + guildsnowflake: { toString: () => '123456789' }, + usersnowflake: { toString: () => '987654321' }, + cmd: { toString: () => 'ban' }, + }, + { + guildsnowflake: { toString: () => '123456789' }, + usersnowflake: { toString: () => '987654321' }, + cmd: { toString: () => 'kick' }, + }, + ]; + + mockBlacklistFind.mockResolvedValue(mockBlacklists); + (mockCacheInstance.cacheable as jest.MockedFunction).mockImplementation(async (fn: () => Promise) => await fn()); + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useBlacklist(); + + // Call buildCache indirectly through getBlacklist + await result.actions.getBlacklist('123456789', '987654321', 'ban'); + + expect(mockBlacklistFind).toHaveBeenCalled(); + }); + + describe('getBlacklist', () => { + beforeEach(() => { + mockCacheInstance.isCached.mockReturnValue(true); + }); + + it('should return false when user is not blacklisted', async () => { + const mockBlacklistData = { + '123456789': { + '987654321': ['kick'], + }, + '0': { + '987654321': [], + }, + }; + + (mockCacheInstance.cacheable as jest.MockedFunction).mockResolvedValue(mockBlacklistData); + + const result = useBlacklist(); + const isBlacklisted = await result.actions.getBlacklist('123456789', '987654321', 'ban'); + + expect(isBlacklisted).toBe(false); + }); + + it('should handle exceptions gracefully and return false', async () => { + (mockCacheInstance.cacheable as jest.MockedFunction).mockResolvedValue({}); + + const result = useBlacklist(); + const isBlacklisted = await result.actions.getBlacklist('123456789', '987654321', 'ban'); + + expect(isBlacklisted).toBe(false); + }); + }); + + describe('setBlacklist', () => { + beforeEach(() => { + mockCacheInstance.isCached.mockReturnValue(true); + mockBlacklistSave.mockResolvedValue(); + }); + + it('should create and save new blacklist entry', async () => { + const result = useBlacklist(); + await result.actions.setBlacklist('123456789', '987654321', 'ban'); + + expect(mockBlacklistSave).toHaveBeenCalled(); + expect(mockCacheInstance.delete).toHaveBeenCalledWith('blacklist'); + }); + }); + + describe('clearBlacklist', () => { + beforeEach(() => { + mockCacheInstance.isCached.mockReturnValue(true); + mockBlacklistDeleteOne.mockResolvedValue({ deletedCount: 1 }); + }); + + it('should delete blacklist entry', async () => { + const result = useBlacklist(); + await result.actions.clearBlacklist('123456789', '987654321', 'ban'); + + expect(mockBlacklistDeleteOne).toHaveBeenCalled(); + expect(mockCacheInstance.delete).toHaveBeenCalledWith('blacklist'); + }); + }); +}); \ No newline at end of file diff --git a/src/lib/hooks/__tests__/useGuildConfig.test.ts b/src/lib/hooks/__tests__/useGuildConfig.test.ts new file mode 100644 index 0000000..3cbeb4c --- /dev/null +++ b/src/lib/hooks/__tests__/useGuildConfig.test.ts @@ -0,0 +1,209 @@ +import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; + +// Mock GuildConfig schema - declare mocks before jest.mock +const mockGuildConfigSave = jest.fn() as jest.MockedFunction<() => Promise>; +const mockGuildConfigFind = jest.fn() as jest.MockedFunction<() => Promise>; + +// Create mock instances that will be used by the module +const mockCacheInstance = { + cacheable: jest.fn() as jest.MockedFunction, + isCached: jest.fn() as jest.MockedFunction, + delete: jest.fn() as jest.MockedFunction, +}; + +const mockLogger = { + info: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + debug: jest.fn(), +}; + +// Mock Cacheables +jest.mock('cacheables', () => ({ + Cacheables: jest.fn().mockImplementation(() => mockCacheInstance), +})); + +// Mock Arc3 +jest.mock('../../arc/arc3.js', () => ({ + Arc3: { + Arc3: { + clientLogger: { + child: jest.fn(() => mockLogger), + }, + }, + }, +})); + +// Mock mongoose and mongoose-long +jest.mock('mongoose', () => ({ + __esModule: true, + default: { + Types: { + Long: { + fromString: jest.fn((str) => ({ toString: () => str })), + }, + ObjectId: { + createFromTime: jest.fn((time) => ({ _id: `generated_${time}` })), + }, + }, + }, + Types: { + Long: { + fromString: jest.fn((str) => ({ toString: () => str })), + }, + ObjectId: { + createFromTime: jest.fn((time) => ({ _id: `generated_${time}` })), + }, + }, +})); + +jest.mock('mongoose-long', () => ({ + __esModule: true, + default: jest.fn().mockImplementation((mongoose) => mongoose), +})); + +jest.mock('../../schema/v1/GuildConfig.js', () => { + const mockConstructor = jest.fn().mockImplementation((data: any) => ({ + ...(data || {}), + save: mockGuildConfigSave, + })); + + // Add static methods to the constructor + Object.assign(mockConstructor, { + find: mockGuildConfigFind, + }); + + return { default: mockConstructor }; +}); + +import { useGuildConfig } from '../useGuildConfig'; + +describe('useGuildConfig', () => { + beforeEach(() => { + // Reset all mocks + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize correctly and return actions and states', () => { + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useGuildConfig(); + + expect(result).toHaveProperty('actions'); + expect(result).toHaveProperty('states'); + expect(result.actions).toHaveProperty('getGuildConfig'); + expect(result.actions).toHaveProperty('getConfig'); + expect(result.actions).toHaveProperty('setConfig'); + expect(result.actions).toHaveProperty('buildCache'); + expect(result.states).toHaveProperty('guildConfigCache'); + expect(result.states).toHaveProperty('logger'); + }); + + it('should build cache correctly from database records', async () => { + const mockGuildConfigs = [ + { + guildsnowflake: { toString: () => '123456789' }, + configkey: 'modmail_channel', + configvalue: '987654321', + }, + { + guildsnowflake: { toString: () => '123456789' }, + configkey: 'log_channel', + configvalue: '111111111', + }, + ]; + + mockGuildConfigFind.mockResolvedValue(mockGuildConfigs); + (mockCacheInstance.cacheable as jest.MockedFunction).mockImplementation(async (fn: () => Promise) => await fn()); + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useGuildConfig(); + const configData = await result.actions.buildCache(); + + expect(mockGuildConfigFind).toHaveBeenCalled(); + expect(configData).toBeDefined(); + }); + + describe('getConfig', () => { + beforeEach(() => { + mockCacheInstance.isCached.mockReturnValue(true); + }); + + it('should handle non-existent guild gracefully', async () => { + const mockConfigData = { + '123456789': { + 'modmail_channel': '987654321', + }, + }; + + (mockCacheInstance.cacheable as jest.MockedFunction).mockResolvedValue(mockConfigData); + + const result = useGuildConfig(); + + // This should not throw an error even if guild doesn't exist + await expect(result.actions.getConfig('999999999', 'modmail_channel')).resolves.not.toThrow(); + }); + }); + + describe('getGuildConfig', () => { + beforeEach(() => { + mockCacheInstance.isCached.mockReturnValue(true); + }); + + it('should handle non-existent guild gracefully', async () => { + const mockConfigData = { + '123456789': { + 'modmail_channel': '987654321', + }, + }; + + (mockCacheInstance.cacheable as jest.MockedFunction).mockResolvedValue(mockConfigData); + + const result = useGuildConfig(); + + // This should not throw an error even if guild doesn't exist + await expect(result.actions.getGuildConfig('999999999')).resolves.not.toThrow(); + }); + }); + + describe('setConfig', () => { + beforeEach(() => { + mockCacheInstance.isCached.mockReturnValue(true); + mockGuildConfigSave.mockResolvedValue(); + }); + + it('should create and save new config entry', async () => { + const result = useGuildConfig(); + await result.actions.setConfig('123456789', 'modmail_channel', '987654321'); + + expect(mockGuildConfigSave).toHaveBeenCalled(); + expect(mockCacheInstance.delete).toHaveBeenCalledWith('guildConfigs'); + }); + + it('should handle save errors', async () => { + const mockError = new Error('Database save failed'); + mockGuildConfigSave.mockRejectedValueOnce(mockError); + + const result = useGuildConfig(); + + await expect(result.actions.setConfig('123456789', 'modmail_channel', '987654321')) + .rejects.toThrow('Database save failed'); + }); + }); + + it('should handle empty config response', async () => { + mockGuildConfigFind.mockResolvedValue([]); + (mockCacheInstance.cacheable as jest.MockedFunction).mockImplementation(async (fn: () => Promise) => await fn()); + mockCacheInstance.isCached.mockReturnValue(true); + + const result = useGuildConfig(); + const configData = await result.actions.buildCache(); + + expect(configData).toEqual({}); + expect(mockGuildConfigFind).toHaveBeenCalledWith(); + }); +}); \ No newline at end of file diff --git a/src/lib/hooks/__tests__/useTextContent.test.ts b/src/lib/hooks/__tests__/useTextContent.test.ts new file mode 100644 index 0000000..029197c --- /dev/null +++ b/src/lib/hooks/__tests__/useTextContent.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; + +// Mock fs module - define inline to ensure it's available during module import +jest.mock('fs', () => ({ + readFileSync: jest.fn().mockImplementation((path) => { + const pathStr = path as string; + let content = ''; + if (pathStr.includes('en.json')) { + content = JSON.stringify({ + 'arc.bot.name': 'ARC', + 'arc.blacklist': 'You are blacklisted from using that command!', + 'arc.command.setconfig.sucess': 'Config has been successfully set', + 'arc.modmail.blacklisted': 'You are blacklisted from using modmail', + 'arc.modmail.channel.name': 'Modmail', + 'arc.modmail.delivery.emoji.delivered': '📨', + 'arc.modmail.delivery.emoji.failed': '🔴', + 'arc.modmail.delivery.recieved.description': 'Your modmail request was received! Please wait and a staff member will assist you shortly.', + 'arc.modmail.delivery.recieved.footer': 'v0.1 Thank you for using ARC', + 'arc.modmail.selectmenu.placeholder': 'Select a server to modmail: ', + }); + } else if (pathStr.includes('fr.json')) { + content = JSON.stringify({ + 'arc.bot.name': 'ARC', + 'arc.blacklist': 'Vous êtes sur liste noire pour cette commande!', + 'arc.command.setconfig.sucess': 'La configuration a été définie avec succès', + 'arc.modmail.blacklisted': 'Vous êtes sur liste noire pour le modmail', + 'arc.modmail.channel.name': 'Modmail', + 'arc.modmail.delivery.emoji.delivered': '📨', + 'arc.modmail.delivery.emoji.failed': '🔴', + 'arc.modmail.delivery.recieved.description': 'Votre demande de modmail a été reçue! Veuillez patienter, un membre du personnel vous aidera sous peu.', + 'arc.modmail.delivery.recieved.footer': 'v0.1 Merci d\'utiliser ARC', + 'arc.modmail.selectmenu.placeholder': 'Sélectionnez un serveur pour le modmail: ', + }); + } else { + content = JSON.stringify({}); + } + + return { + toString: () => content + }; + }), + readdirSync: jest.fn().mockReturnValue(['en.json', 'fr.json', 'zh.json']), +})); + +import { useTextContent, Locale, Translations } from '../useTextContent'; + +describe('useTextContent', () => { + + beforeEach(() => { + // Reset all mocks + jest.clearAllMocks(); + + // Mock console.log to avoid clutter in test output + jest.spyOn(console, 'log').mockImplementation(() => {}); + + // The mock is already set up in the jest.mock call above + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('Locale enum', () => { + it('should have correct locale values', () => { + expect(Locale.EN).toBe('en'); + expect(Locale.FR).toBe('fr'); + expect(Locale.ZH).toBe('zh'); + }); + }); + + describe('useTextContent hook', () => { + it('should initialize correctly with English locale', () => { + const result = useTextContent(Locale.EN); + + expect(result).toHaveProperty('actions'); + expect(result).toHaveProperty('states'); + expect(result.actions).toHaveProperty('text'); + expect(result.states).toHaveProperty('translations'); + }); + + it('should initialize correctly with French locale', () => { + const result = useTextContent(Locale.FR); + + expect(result).toHaveProperty('actions'); + expect(result).toHaveProperty('states'); + expect(result.actions).toHaveProperty('text'); + expect(result.states).toHaveProperty('translations'); + }); + + it('should have translations loaded', () => { + const result = useTextContent(Locale.EN); + const translations = result.states.translations; + + // Check that translations is an object and has the expected properties + expect(typeof translations).toBe('object'); + expect(translations).toBeTruthy(); + + // The actual implementation may have different structure due to enum iteration + // Let's just verify that we get some translation data + expect(Object.keys(translations).length).toBeGreaterThan(0); + }); + + describe('text function', () => { + it('should call text function without errors', () => { + const result = useTextContent(Locale.EN); + + const textResult = result.actions.text('arc.bot.name'); + + expect(textResult).toBe("ARC"); + }); + + it('should handle text function with placeholders', () => { + const result = useTextContent(Locale.EN); + + // Temporarily modify translations to test placeholder functionality + (result.states.translations as any)['arc.test.placeholder'] = 'Hello {0}, welcome to {1}!'; + + const text = result.actions.text('arc.test.placeholder' as keyof Translations, 'User', 'ARC'); + + expect(text).toBe('Hello User, welcome to ARC!'); + + }); + + it('should return code if translation key not found', () => { + const result = useTextContent(Locale.EN); + const textResult = result.actions.text('non.existent.key' as keyof Translations); + expect(textResult).toBe('non.existent.key'); + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/src/lib/hooks/useActiveModmails.ts b/src/lib/hooks/useActiveModmails.ts new file mode 100644 index 0000000..012c991 --- /dev/null +++ b/src/lib/hooks/useActiveModmails.ts @@ -0,0 +1,34 @@ +import { Cacheables } from "cacheables"; +import { Arc3 } from "../arc3.js" +import Modmail from "../schema/v1/Modmail.js"; + +const activeModmailsCache = new Cacheables({ + logTiming: false, + log: false +}); + +const logger = Arc3.Arc3.clientLogger.child("useActiveModmails"); + +export const useActiveModmails = () => { + + async function buildCache() { + return activeModmailsCache.cacheable(async () => { + return await Modmail.find(); + }, "activemodmails", { cachePolicy: 'cache-only'}) + } + + if (!activeModmailsCache.isCached("activemodmails")) { + buildCache(); + } + + return { + actions: { + buildCache + }, + states: { + activeModmailsCache, + logger + } + } + +} \ No newline at end of file diff --git a/src/lib/hooks/useBlacklist.ts b/src/lib/hooks/useBlacklist.ts new file mode 100644 index 0000000..981d2a2 --- /dev/null +++ b/src/lib/hooks/useBlacklist.ts @@ -0,0 +1,99 @@ +import { Cacheables } from "cacheables"; +import { Arc3 } from "../arc3.js" +import Blacklist from "../schema/v1/Blacklist.js"; + +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' +mongooseLong(mongoose); +const { Types: { Long, ObjectId} } = mongoose; + + +const blacklistCache = new Cacheables({ + logTiming: false, + log: false +}); + +const logger = Arc3.Arc3.clientLogger.child("useBlacklist"); + +export const useBlacklist = () => { + if ( !blacklistCache.isCached("blacklist") ) { + buildCache().then( x => { + logger.info("Building blacklist cache...") + }) + } + + async function buildCache() { + + return blacklistCache.cacheable(async () => { + + const blacklist: Record> = {}; + const blacklists = await Blacklist.find(); + + blacklists.forEach(x => { + + const guildSnowflake = x.guildsnowflake?.toString()?? "undefined"; + const blacklistKey = x.cmd?.toString()?? "undefined"; + const userSnowflake = x.usersnowflake?.toString()?? "undefined"; + + if (!(guildSnowflake in blacklist)) + blacklist[guildSnowflake] = {} + + if (!(userSnowflake in blacklist[guildSnowflake])) + blacklist[guildSnowflake][userSnowflake] = [] + + blacklist[guildSnowflake][userSnowflake].push(blacklistKey); + + }); + + return blacklist; + + }, 'blacklist', { cachePolicy: 'cache-only'}); + + } + + async function getBlacklist(guildSnowflake: string, userSnowflake: string, key: string) : Promise { + const blacklist = await buildCache(); + try { + return blacklist[guildSnowflake][userSnowflake].includes(key) || blacklist['0'][userSnowflake].includes(key); + } catch { + return false; + } + } + + async function setBlacklist(guildSnowflake: string, userSnowflake: string, key: string) { + + const blacklist = new Blacklist({ + _id: ObjectId.createFromTime(new Date().getTime()), + guildsnowflake: Long.fromString(guildSnowflake), + usersnowflake: Long.fromString(userSnowflake), + cmd: key + }); + + await blacklist.save(); + blacklistCache.delete('blacklist'); + + } + + async function clearBlacklist(guildSnowflake: string, userSnowflake: string, key: string) { + await Blacklist.deleteOne({ + guildsnowflake: Long.fromString(guildSnowflake), + usersnowflake: Long.fromString(userSnowflake), + cmd: key + }); + blacklistCache.delete('blacklist'); + } + + return { + actions: { + getBlacklist, + setBlacklist, + clearBlacklist, + }, + states: { + blacklistCache, + logger + } + } + + +} \ No newline at end of file diff --git a/src/lib/hooks/useGuildConfig.ts b/src/lib/hooks/useGuildConfig.ts new file mode 100644 index 0000000..acfa44c --- /dev/null +++ b/src/lib/hooks/useGuildConfig.ts @@ -0,0 +1,95 @@ +import { Cacheables } from "cacheables" +import GuildConfig from "../schema/v1/GuildConfig.js"; +import { Arc3 } from "../arc3.js" + +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' +mongooseLong(mongoose); +const { Types: { Long, ObjectId} } = mongoose; + + +const guildConfigCache = new Cacheables({ + logTiming: false, + log: false, +}); + +const logger = Arc3.Arc3.clientLogger.child("useGuildCache"); + +export const useGuildConfig = () => { + + if ( !guildConfigCache.isCached('guildConfigs') ) { + + buildCache().then( x => { + logger.info("Building guild config cache..."); + }) + + } + + async function buildCache() { + + return guildConfigCache.cacheable(async () => { + + const config : Record> = {}; + const guildConfigs = await GuildConfig.find(); + + guildConfigs.forEach(x => { + + const guildSnowflake = x.guildsnowflake?.toString() ?? "undefined"; + const configKey : GuildConfigKey = (x.configkey ?? "unknown") as GuildConfigKey; + + if (!config[guildSnowflake]) + config[guildSnowflake] = {} as Record; + + config[guildSnowflake][configKey] = x.configvalue ?? "undefined"; + + }) + + return config; + + }, 'guildConfigs', { cachePolicy: 'cache-only'}); + } + + async function setConfig(guildSnowflake: string, configKey: string, configValue: string) { + + const config = new GuildConfig({ + _id: ObjectId.createFromTime(new Date().getTime()), + guildsnowflake: Long.fromString(guildSnowflake), + configkey: configKey, + configvalue: configValue + }); + + await config.save(); + guildConfigCache.clear(); + await buildCache(); + + } + + async function getConfig(guildSnowflake: string, configKey: GuildConfigKey) { + const config = await buildCache(); + return config[guildSnowflake]?.[configKey]; + } + + async function getGuildConfig(guildSnowflake: string) { + const config = await buildCache(); + return config[guildSnowflake]; + } + + return { + actions: { + getGuildConfig, + getConfig, + setConfig, + buildCache + }, + states: { + guildConfigCache, + logger + } + } + +} + +export enum GuildConfigKey { + modmailchannel = 'modmailchannel', + transcriptchannel = 'transcriptchannel', +}; \ No newline at end of file diff --git a/src/lib/hooks/useTextContent.ts b/src/lib/hooks/useTextContent.ts new file mode 100644 index 0000000..a1eb5c7 --- /dev/null +++ b/src/lib/hooks/useTextContent.ts @@ -0,0 +1,97 @@ +import { readdirSync, readFileSync } from "fs"; + +export enum Locale { + EN = 'en', + FR = 'fr', + ZH = 'zh' +} + +const translationFiles : Record = {} + +for (let locale in Locale) { + locale = locale.toLowerCase(); + translationFiles[locale] = JSON.parse(readFileSync(`./src/locales/${locale}.json`).toString()) as Translations; +} + +export const useTextContent = (locale: Locale) => { + + const translations = translationFiles[locale]; + + function text(code: keyof Translations, ...args: string[]): string { + + if (!(code in translations)) + return code; + + let textContent = translations[code]; + const argsRegexp = /{\d}/g + + const results = textContent.match(argsRegexp); + + if (results) { + results?.forEach((x : string) => { + const index = parseInt(x.replace(/[{}]/g, '')); + textContent = textContent.replace(x, args[index]); + }) + } + + return textContent; + } + + return { + actions: { + text + }, + states: { + translations + } + } + +} + +export interface Translations { + + // Bot General + 'arc.blacklist': string; // "You are blacklisted from using that command!" + 'arc.bot.name': string; // ARC + + // Commands + 'arc.command.setconfig.sucess': string; // Config has been sucessfully set + + // Modmail - General + 'arc.modmail.blacklisted': string; // "You are blacklisted from using modmail" + 'arc.modmail.channel.name': string; // Modmail + + // Modmail - Delivery + 'arc.modmail.delivery.emoji.delivered': string; // "📨" + 'arc.modmail.delivery.emoji.failed': string; // "🔴" + 'arc.modmail.delivery.emoji.edited': string; // "✏️" + 'arc.modmail.delivery.recieved.description': string; // "Your modmail request was recieved! Please wait and a staff member will assist you shortly." + 'arc.modmail.delivery.recieved.footer': string; // v0.1 Thank you for using ARC + + // Modmail - Menu Buttons + 'arc.modmail.menu.button.ban': string; // Ban + 'arc.modmail.menu.button.ban.emoji': string; // 🔨 + 'arc.modmail.menu.button.ping': string; // Ping + 'arc.modmail.menu.button.ping.emoji': string; // 📣 + 'arc.modmail.menu.button.save': string; // Save + 'arc.modmail.menu.button.save.emoji': string; // 💾 + + + // Modmail - Menu General + 'arc.modmail.menu.description': string; // A modmail session was opened with <@{0}> + 'arc.modmail.menu.footer': string; // ARC v{0} - Modmail + 'arc.modmail.menu.select.placeholder': string; // "Select a server to modmail: " + 'arc.modmail.menu.title': string; // Modmail + + //Modmail - Failed + 'arc.modmail.failed.title': string; // Modmail Failed + 'arc.modmail.failed.description': string; // There was an error while trying to open your modmail. Please try again later. + + // Modmail - Transcript + 'arc.modmail.transcript.title': string; // Modmail Transcript + 'arc.modmail.transcript.description': string; // **Modmail with:** <@{0}>\n**Saved** **by** <@{2}>\n\n[Transcript]({3}) + + // Modmail - Moderator Messages + 'arc.modmail.moderator.image': string; // Image: + +} diff --git a/src/lib/logger/DiscordLoggerImpl.ts b/src/lib/logger/DiscordLoggerImpl.ts new file mode 100644 index 0000000..47d9cb3 --- /dev/null +++ b/src/lib/logger/DiscordLoggerImpl.ts @@ -0,0 +1,33 @@ +import { ILogger } from "discordx"; +import { type Logger, pino } from 'pino'; + +export class DiscordLoggerImpl implements ILogger { + + private readonly _logger : Logger; + + constructor(loggerName: string, logLevel: string) { + this._logger = pino({ level: logLevel }) + .child( { source: loggerName } ); + } + + child(loggerName: string) : Logger { + return this._logger.child({ + source: loggerName + }); + } + + error(...args: unknown[]): void { + this._logger.error(args); + } + + info(...args: unknown[]): void { + this._logger.info(args); + } + log(...args: unknown[]): void { + this._logger.debug(args); + } + warn(...args: unknown[]): void { + this._logger.warn(args); + } + +} diff --git a/src/lib/logger/index.ts b/src/lib/logger/index.ts new file mode 100644 index 0000000..c54be5f --- /dev/null +++ b/src/lib/logger/index.ts @@ -0,0 +1,3 @@ +import { DiscordLoggerImpl } from "./DiscordLoggerImpl.js"; + +export {DiscordLoggerImpl as Logger} \ No newline at end of file diff --git a/src/lib/repositories/ModmailRepo.ts b/src/lib/repositories/ModmailRepo.ts new file mode 100644 index 0000000..9d14d38 --- /dev/null +++ b/src/lib/repositories/ModmailRepo.ts @@ -0,0 +1,130 @@ +import { Logger } from "pino"; +import { useActiveModmails } from "../hooks/useActiveModmails.js"; +import { Arc3 } from "../arc3.js"; +import Modmail from "../schema/v1/Modmail.js"; + +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' +import Transcript from "../schema/v2/Transcript.js"; + +mongooseLong(mongoose); +const { Types: { Long, ObjectId} } = mongoose; + + +export class ModmailRepo { + + private logger : Logger; + private activeModmail = useActiveModmails(); + private recentMessagesCache : Record; + + /** + * ModmailEvents class constructor + * Initializes the logger and modmail cache. + * @constructor + */ + constructor() { + + this.logger = Arc3.Arc3.clientLogger.child("ModmailRepo"); + + this.initCaches().then( _ => { + this.logger.info("Cache initialized"); + }); + + this.recentMessagesCache = { + + } as Record; + + } + + /** + * Initializes the caches for modmail events. + * It is called once when the class is instantiated. + */ + private async initCaches() { + await this.getActiveModmails(); + } + + /** + * Retrieves active modmails from the database and caches them. + * @returns {Promise} A promise that resolves to an array of active modmails. + */ + public getActiveModmails = () => { + return this.activeModmail.actions.buildCache(); + } + + /** + * Clears the active modmails cache. + * @returns {void} + */ + public clearActiveModmailsCache() { + const { states: { activeModmailsCache } } = this.activeModmail; + activeModmailsCache.clear(); + } + + /** + * Creates a new modmail in the database. + * @param {string} userId - The user ID. + * @param {string} channelId - The channel ID. + * @param {string} webhookId - The webhook ID. + * @returns {Promise>} A promise that resolves to the created modmail. + */ + public async CreateModmail(userId: string, channelId: string, webhookId: string) : Promise> { + + const { states: { activeModmailsCache } } = this.activeModmail; + + const modmail = new Modmail({ + _id: ObjectId.createFromTime(new Date().getTime()), + usersnowflake: Long.fromString(userId), + channelsnowflake: Long.fromString(channelId), + webhooksnowflake: Long.fromString(webhookId) + }); + + await modmail.save(); + activeModmailsCache.clear() + return modmail; + + } + + async CreateTranscript( + modmail: InstanceType, + senderSnowflake: string, + attachments: string[], + createdAt: Date, + messageContent: string, + guildSnowflake: string, + transcriptType: string, + comment: boolean, + messageId: string, + ) { + + const transcript = new Transcript({ + modmailId: modmail._id?.toString(), + sendersnowflake: senderSnowflake, + attachments: attachments, + createdat: createdAt, + GuildSnowflake: guildSnowflake, + messagecontent: messageContent, + transcripttype: transcriptType, + comment: comment, + messageid: messageId + }); + + await transcript.save(); + return transcript; + + } + + public addRecentMessage(userMessageId: string, modmailMessageId: string) { + this.recentMessagesCache[userMessageId] = modmailMessageId; + } + + public getRecentModmailMessageID(userMessageID: string) { + return this.recentMessagesCache[userMessageID]; + } + + public getRecentUserMessageID(modmailMessageID: string) { + const entry = Object.entries(this.recentMessagesCache).find( ([_, v]) => v === modmailMessageID); + return entry ? entry[0] : undefined; + } + +} \ No newline at end of file diff --git a/src/schema/v1/Appeal.js b/src/lib/schema/v1/Appeal.ts similarity index 79% rename from src/schema/v1/Appeal.js rename to src/lib/schema/v1/Appeal.ts index 5ac927f..af3ed0f 100644 --- a/src/schema/v1/Appeal.js +++ b/src/lib/schema/v1/Appeal.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const AppealSchema = new mongoose.Schema({ _id: String, @@ -11,4 +11,4 @@ const AppealSchema = new mongoose.Schema({ const Appeal = mongoose.model('Appeal', AppealSchema); -module.exports = Appeal; \ No newline at end of file +export default Appeal; \ No newline at end of file diff --git a/src/schema/v1/Application.js b/src/lib/schema/v1/Application.ts similarity index 83% rename from src/schema/v1/Application.js rename to src/lib/schema/v1/Application.ts index 03caca4..8f49cb6 100644 --- a/src/schema/v1/Application.js +++ b/src/lib/schema/v1/Application.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const ApplicationSchema = new mongoose.Schema({ guildSnowflake: String, @@ -13,8 +13,8 @@ const ApplicationSchema = new mongoose.Schema({ about: String, age: String, joindate: String -}) +}); const Application = mongoose.model('Application', ApplicationSchema); -module.exports = Application; \ No newline at end of file +export default Application; \ No newline at end of file diff --git a/src/schema/v1/Approval.js b/src/lib/schema/v1/Approval.ts similarity index 76% rename from src/schema/v1/Approval.js rename to src/lib/schema/v1/Approval.ts index dc71949..de50e04 100644 --- a/src/schema/v1/Approval.js +++ b/src/lib/schema/v1/Approval.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const ApprovalSchema = new mongoose.Schema({ guildSnowflake: String, @@ -9,4 +9,4 @@ const ApprovalSchema = new mongoose.Schema({ const Approval = mongoose.model('Approval', ApprovalSchema); -module.exports = Approval; \ No newline at end of file +export default Approval; \ No newline at end of file diff --git a/src/lib/schema/v1/Blacklist.ts b/src/lib/schema/v1/Blacklist.ts new file mode 100644 index 0000000..e898bb5 --- /dev/null +++ b/src/lib/schema/v1/Blacklist.ts @@ -0,0 +1,17 @@ +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' + +mongooseLong(mongoose); + +const { Types: { Long, ObjectId} } = mongoose; + +const blacklistSchema = new mongoose.Schema({ + _id: ObjectId, + usersnowflake: Long, + cmd: String, + guildsnowflake: Long +}) + +const Blacklist = mongoose.model("Blacklist", blacklistSchema); + +export default Blacklist; \ No newline at end of file diff --git a/src/schema/v1/Commandstat.js b/src/lib/schema/v1/Commandstat.ts similarity index 78% rename from src/schema/v1/Commandstat.js rename to src/lib/schema/v1/Commandstat.ts index ecca498..8868237 100644 --- a/src/schema/v1/Commandstat.js +++ b/src/lib/schema/v1/Commandstat.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const commandStatSchema = new mongoose.Schema({ _id: String, @@ -11,4 +11,4 @@ const commandStatSchema = new mongoose.Schema({ const CommandStat = mongoose.model("Commandstat", commandStatSchema); -module.exports = CommandStat; \ No newline at end of file +export default CommandStat; \ No newline at end of file diff --git a/src/schema/v1/Comment.js b/src/lib/schema/v1/Comment.ts similarity index 76% rename from src/schema/v1/Comment.js rename to src/lib/schema/v1/Comment.ts index 2d1021e..6da88c1 100644 --- a/src/schema/v1/Comment.js +++ b/src/lib/schema/v1/Comment.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const CommentSchema = new mongoose.Schema({ userSnowflake: String, @@ -9,4 +9,4 @@ const CommentSchema = new mongoose.Schema({ const Comment = mongoose.model('Comment', CommentSchema); -module.exports = Comment; \ No newline at end of file +export default Comment; \ No newline at end of file diff --git a/src/schema/v1/Guild.js b/src/lib/schema/v1/Guild.ts similarity index 68% rename from src/schema/v1/Guild.js rename to src/lib/schema/v1/Guild.ts index b9103a5..e27b9f1 100644 --- a/src/schema/v1/Guild.js +++ b/src/lib/schema/v1/Guild.ts @@ -1,5 +1,7 @@ -const mongoose = require('mongoose'); -require('mongoose-long')(mongoose); +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' + +mongooseLong(mongoose); const { Types: { Long, ObjectId} } = mongoose; @@ -14,4 +16,4 @@ const guildSchema = new mongoose.Schema({ const Guild = mongoose.model("Guild", guildSchema, "Guilds"); -module.exports = Guild; \ No newline at end of file +export default Guild; \ No newline at end of file diff --git a/src/lib/schema/v1/GuildConfig.ts b/src/lib/schema/v1/GuildConfig.ts new file mode 100644 index 0000000..2964e6d --- /dev/null +++ b/src/lib/schema/v1/GuildConfig.ts @@ -0,0 +1,18 @@ +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' + +mongooseLong(mongoose); + +const { Types: { Long, ObjectId} } = mongoose; + +const guildConfigSchema = new mongoose.Schema({ + _id: ObjectId, + guildsnowflake: Long, + configkey: String, + configvalue: String +}); + +const GuildConfig = mongoose.model("GuildConfig", guildConfigSchema); + +export default GuildConfig; + diff --git a/src/schema/v1/Insight.js b/src/lib/schema/v1/Insight.ts similarity index 80% rename from src/schema/v1/Insight.js rename to src/lib/schema/v1/Insight.ts index 1641d05..7be6e1e 100644 --- a/src/schema/v1/Insight.js +++ b/src/lib/schema/v1/Insight.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const insightSchema = new mongoose.Schema({ _id: String, @@ -14,4 +14,4 @@ const insightSchema = new mongoose.Schema({ const Insight = mongoose.model("Insight", insightSchema); -module.exports = Insight; \ No newline at end of file +export default Insight; \ No newline at end of file diff --git a/src/lib/schema/v1/Modmail.ts b/src/lib/schema/v1/Modmail.ts new file mode 100644 index 0000000..464349d --- /dev/null +++ b/src/lib/schema/v1/Modmail.ts @@ -0,0 +1,17 @@ +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' + +mongooseLong(mongoose); + +const { Types: { Long, ObjectId } } = mongoose; + +const ModmailSchema = new mongoose.Schema({ + _id: ObjectId, + channelsnowflake: Long, + webhooksnowflake: Long, + usersnowflake: Long +}, { timestamps: true }); + +const Modmail = mongoose.model("Modmail", ModmailSchema); + +export default Modmail; \ No newline at end of file diff --git a/src/schema/v1/Notes.js b/src/lib/schema/v1/Notes.ts similarity index 78% rename from src/schema/v1/Notes.js rename to src/lib/schema/v1/Notes.ts index e45082c..7d1e4d7 100644 --- a/src/schema/v1/Notes.js +++ b/src/lib/schema/v1/Notes.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const userNoteSchema = new mongoose.Schema({ _id: String, @@ -11,4 +11,4 @@ const userNoteSchema = new mongoose.Schema({ const UserNote = mongoose.model('user_note', userNoteSchema) -module.exports = UserNote; \ No newline at end of file +export default UserNote; \ No newline at end of file diff --git a/src/schema/v1/Transcript.js b/src/lib/schema/v1/Transcript.ts similarity index 82% rename from src/schema/v1/Transcript.js rename to src/lib/schema/v1/Transcript.ts index 09d048a..6b3e382 100644 --- a/src/schema/v1/Transcript.js +++ b/src/lib/schema/v1/Transcript.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const TranscriptSchema = new mongoose.Schema({ @@ -14,4 +14,4 @@ const TranscriptSchema = new mongoose.Schema({ const Transcript = mongoose.model('Transcript', TranscriptSchema); -module.exports = Transcript; \ No newline at end of file +export default Transcript; \ No newline at end of file diff --git a/src/lib/schema/v2/Transcript.ts b/src/lib/schema/v2/Transcript.ts new file mode 100644 index 0000000..5084d78 --- /dev/null +++ b/src/lib/schema/v2/Transcript.ts @@ -0,0 +1,20 @@ +import mongoose from 'mongoose'; + +const TranscriptSchema = new mongoose.Schema({ + + modmailId: String, + sendersnowflake: String, + attachments: [String], + createdat: Date, + GuildSnowflake: String, + messagecontent: String, + transcripttype: String, + comment: Boolean, + messageid: String +}, { + collection: "transcripts" +}); + +const Transcript = mongoose.model('Transcript', TranscriptSchema); + +export default Transcript; \ No newline at end of file diff --git a/src/schema/v2/UserData.js b/src/lib/schema/v2/UserData.ts similarity index 76% rename from src/schema/v2/UserData.js rename to src/lib/schema/v2/UserData.ts index 303f308..c5032e0 100644 --- a/src/schema/v2/UserData.js +++ b/src/lib/schema/v2/UserData.ts @@ -1,4 +1,4 @@ -const mongoose = require('mongoose'); +import mongoose from 'mongoose'; const userDataSchema = new mongoose.Schema({ _id: String, @@ -10,4 +10,4 @@ const userDataSchema = new mongoose.Schema({ const UserData = mongoose.model('userdata', userDataSchema) -module.exports = UserData; \ No newline at end of file +export default UserData; \ No newline at end of file diff --git a/src/lib/service/ModmailInteractionService.ts b/src/lib/service/ModmailInteractionService.ts new file mode 100644 index 0000000..c707539 --- /dev/null +++ b/src/lib/service/ModmailInteractionService.ts @@ -0,0 +1,113 @@ +import { Logger } from "pino"; +import { ModmailRepo } from "../repositories/ModmailRepo.js"; +import { Arc3 } from "../arc3.js"; +import { useBlacklist } from "../hooks/useBlacklist.js"; +import { Locale, useTextContent } from "../hooks/useTextContent.js"; +import { initModmailAsync, LogModmailTranscript, TryCleanupModmail } from "../util/ModmailUtils.js"; +import { ButtonInteraction, StringSelectMenuInteraction, TextChannel } from "discord.js"; +import { ModmailMenuEmbed, ModmailSentEmbed } from "../../ui/ModmailUi.js"; +import Modmail from "../schema/v1/Modmail.js"; + + +export class ModmailInteractionService { + + private logger: Logger; + private modmailRepo: ModmailRepo; + + constructor(modmailRepo: ModmailRepo) { + this.logger = Arc3.Arc3.clientLogger.child("ModmailInteractionService"); + this.modmailRepo = modmailRepo; + } + + /** + * Handles the modmail server selection interaction. + * @param interaction The StringSelectMenuInteraction object. + * @returns {Promise} + */ + public async ProcessModmailServerSelection(interaction: StringSelectMenuInteraction) { + + const { getBlacklist } = useBlacklist().actions; + const { text } = useTextContent(Locale.EN).actions; + + const selectedGuildId = interaction.values[0]; + const guild = await interaction.client.guilds.fetch(selectedGuildId); + + const isBlacklsted = await getBlacklist(guild.id, interaction.user.id, "modmail"); + + if (isBlacklsted) { + return await interaction.user.send({ + content: text('arc.modmail.blacklisted') + }); + } + + initModmailAsync( + guild, + interaction.user, + this.modmailRepo + ).then(async (modmail) => { + + if (modmail === undefined) { + throw new Error("Modmail init unsucessfull", modmail); + } + + await interaction.editReply({ + embeds: [ModmailSentEmbed()] + // TODO: close button + }); + + const modmailChannel = ( await interaction.client.channels + .fetch(modmail.channelsnowflake?.toString() ?? "0") + ) as TextChannel; + + const modmailMenu = ModmailMenuEmbed(modmail.usersnowflake?.toString()?? "0", modmail._id?.toString()) + + await modmailChannel.send(modmailMenu); + + }) + .catch(async (e) => { + + // Try and find and cleanup the modmail + await TryCleanupModmail(interaction, interaction.user.id, e); + this.modmailRepo.clearActiveModmailsCache() + }); + } + + /** + * Processes the modmail save button interaction. + * @param interaction The ButtonInteraction object. + * @returns {Promise} + */ + public async ProcessModmailSaveButton(interaction: ButtonInteraction) { + + if (!interaction.guild) { + this.logger.warn(`Modmail save button interaction received outside of a guild by user ${interaction.user.tag}`); + return; + } + + const modmailId = interaction.customId.split(".")[2]; + const modmails : InstanceType[] = await this.modmailRepo.getActiveModmails(); + + const modmail = modmails.find(m => m._id?.toString() === modmailId); + + if (!modmail) { + this.logger.warn(`Modmail with ID: ${modmailId} not found for user ${interaction.user.tag}`); + await interaction.reply({ + content: "Modmail session not found or already closed.", + ephemeral: true + }); + return; + } + + // Log the transcript saving attempt + this.logger.info(`User ${interaction.user.tag} is attempting to save transcript for modmail ID: ${modmailId}`); + await LogModmailTranscript(interaction.guild, modmail.usersnowflake?.toString() ?? "0", interaction.user.id); + + // Close the modmail + await TryCleanupModmail(interaction, modmail.usersnowflake?.toString()?? "0", undefined, false, { + _id: modmail._id + }) + this.modmailRepo.clearActiveModmailsCache(); + + } + +} \ No newline at end of file diff --git a/src/lib/service/ModmailMessageService.ts b/src/lib/service/ModmailMessageService.ts new file mode 100644 index 0000000..aedb2c0 --- /dev/null +++ b/src/lib/service/ModmailMessageService.ts @@ -0,0 +1,355 @@ +import { Logger } from "pino"; +import Modmail from "../schema/v1/Modmail"; +import { ModmailRepo } from "../repositories/ModmailRepo"; +import { Arc3 } from "../arc3.js"; +import { SendAttachmentsAndMessageToUser, SendAttachmentsAndMessageToWebhook, SendModmailSelectMenu } from "../util/ModmailUtils.js"; +import { Locale, useTextContent } from "../hooks/useTextContent.js"; +import { Message, PartialMessage } from "discord.js"; +import { ModmailModeratorMessageEmbed } from "../../ui/ModmailUi.js"; + + +export class ModmailMessageService { + + private logger : Logger; + private modmailRepo : ModmailRepo; + + constructor(modmailRepo: ModmailRepo) { + this.logger = Arc3.Arc3.clientLogger.child("ModmailMessageService"); + this.modmailRepo = modmailRepo; + } + + /** + * Processes a modmail message received. + * It checks if the message is in a DM channel and processes it accordingly. + * @param {Message} message - The message object to process. + */ + public async ProcessModmailMessageRecieved(this: ModmailMessageService, message: Message) { + + if (message.channel.isDMBased()) + return await this.ProcessModmailDmMessageRecieved(message); + + const activeModmails = await this.modmailRepo.getActiveModmails(); + + if (activeModmails.find(m => m.channelsnowflake?.toString() === message.channelId )) { + return await this.ProcessModmailChannelMessageRecieved(message); + } + + } + + /** + * Processes a modmail message update. + * It checks if the message is in a DM channel and processes it accordingly. + * @param {Message | PartialMessage} oldMessage - The old message object. + * @param {Message} newMessage - The new message object. + * @return {Promise} + */ + public async ProcessModmailMessageUpdated(this: ModmailMessageService, oldMessage: Message | PartialMessage, newMessage: Message) { + + const { text } = useTextContent(Locale.EN).actions; + + if (oldMessage.channel.isDMBased()) { + const correspondingMessage = this.modmailRepo.getRecentModmailMessageID(oldMessage.id); + if (!correspondingMessage) { + return; + } + + this.EditModmailWebhookMessage(oldMessage, newMessage, correspondingMessage) + .then( async _ => { + this.logger.info("Edited modmail webhook message for DM message %s", oldMessage.id); + await newMessage.react(text('arc.modmail.delivery.emoji.edited')); + }) + .catch( e => this.logger.error(e) ); + } else { + const activeModmails = await this.modmailRepo.getActiveModmails(); + const modmail = activeModmails.find(m => m.channelsnowflake?.toString() === newMessage.channelId); + + if (modmail && !newMessage.author.bot) { + await this.HandleModeratorMessageEdit(oldMessage, newMessage, modmail); + } + } + + } + + /** + * Edits a modmail webhook message when the original message is edited. + * @param {Message | PartialMessage} oldMessage - The old message object. + * @param {Message} newMessage - The new message object. + * @param {string} correspondingMessage - The ID of the corresponding webhook message. + * @returns {Promise} + */ + private async EditModmailWebhookMessage(oldMessage: Message | PartialMessage, newMessage: Message, correspondingMessage: string) { + + const modmails = await this.modmailRepo.getActiveModmails(); + const modmail = modmails.find( m => m.usersnowflake?.toString() === oldMessage.author?.id ); + + if (!modmail) { + this.logger.warn("No active modmail found for user %s when processing updated modmail message", oldMessage.author?.id); + return; + } + + const webhook = await Arc3.Arc3.clientInstance.fetchWebhook(modmail?.webhooksnowflake?.toString() ?? "0") + + if (!webhook) { + this.logger.error("No webhook found for modmail %s when processing updated modmail message", modmail._id?.toString()); + return; + } + + await webhook.editMessage(correspondingMessage, { + content: newMessage.content, + embeds: newMessage.embeds, + components: newMessage.components + }); + + await this.modmailRepo.CreateTranscript( + modmail, + newMessage.author.id, + newMessage.attachments.map(x => x.proxyURL ), + newMessage.createdAt, + newMessage.content, + newMessage.guildId?? "0", + "Modmail", + false, + oldMessage.id + ) + } + + /** + * Handles moderator message edits in modmail. + * Edits the existing embed in the user's DM and creates a new transcript entry. + * @param {Message | PartialMessage} oldMessage - The old message object. + * @param {Message} newMessage - The new message object. + * @param {InstanceType} modmail - The modmail object. + */ + private async HandleModeratorMessageEdit(this: ModmailMessageService, oldMessage: Message | PartialMessage, newMessage: Message, modmail: InstanceType) { + + const { text } = useTextContent(Locale.EN).actions; + const correspondingUserMessageId = this.modmailRepo.getRecentModmailMessageID(newMessage.id); + + if (newMessage.content.startsWith('#')) { + await this.modmailRepo.CreateTranscript( + modmail, + newMessage.author.id, + newMessage.attachments.map(x => x.proxyURL), + newMessage.createdAt, + newMessage.content, + newMessage.guildId ?? "0", + "Modmail", + true, + newMessage.id + ); + return; + } + + if (!correspondingUserMessageId) { + this.logger.warn("No corresponding user message found for moderator message edit %s", newMessage.id); + return; + } + + try { + const user = await Arc3.Arc3.clientInstance.users.fetch(modmail.usersnowflake?.toString() ?? "0"); + const dmChannel = await user.createDM(); + const userMessage = await dmChannel.messages.fetch(correspondingUserMessageId); + + const editedEmbed = ModmailModeratorMessageEmbed( + newMessage.author.username, + newMessage.author.displayAvatarURL(), + newMessage.content + ); + + await userMessage.edit({ embeds: [editedEmbed] }); + await newMessage.react(text('arc.modmail.delivery.emoji.edited')); + + await this.modmailRepo.CreateTranscript( + modmail, + newMessage.author.id, + newMessage.attachments.map(x => x.proxyURL), + newMessage.createdAt, + newMessage.content, + newMessage.guildId ?? "0", + "Modmail", + false, + newMessage.id + ); + + } catch (e) { + this.logger.error(e, "Error editing message in user DM for modmail: " + modmail._id?.toString()); + } + } + + /** + * Processes a modmail message received in a modmail channel. + * It checks if the message is from a bot and processes it accordingly. + * @param {Message} message - The message object to process. + * @returns {Promise} + */ + private async ProcessModmailChannelMessageRecieved(this: ModmailMessageService, message: Message) { + + if (message.author.bot) + return; + + const modmails = await this.modmailRepo.getActiveModmails(); + const modmail = modmails.find(m => m.channelsnowflake?.toString() === message.channelId ); + + if (!modmail) { + this.logger.warn("No modmail found for channel %s", message.channelId); + return; + } + + // Check if message is a comment (starts with #) + if (message.content.startsWith('#')) { + await this.HandleModmailComment(message, modmail); + return; + } + + await this.ProcessModmailMessageToUser(message, modmail); + } + + /** + * Processes a modmail message received in a DM channel. + * It checks if the user has an active modmail and processes the message accordingly. + * If no active modmail is found, it handles the creation of a new modmail. + * @param {Message} message - The message object to process. + */ + private async ProcessModmailDmMessageRecieved( this: ModmailMessageService, message: Message) { + + // We need to guard certain dm messages to save performance. + // If it is from a bot we can safely ignore + if (message.author.bot) + return; + + // Get the active modmails + const modmails = await this.modmailRepo.getActiveModmails(); + const usersWithOpenModmail = modmails.map( (x: InstanceType) => x.usersnowflake?.toString()); + + // Guard that the user has an active modmail + if (!usersWithOpenModmail.includes(message.author.id)) { + await this.HandleNewModmail(message); + return; + } + + if (message.content.toLowerCase() === "close session") + // TODO Handle close modmail + return + + // We have established the user has an active modmail, is not a bot, and does not wish to close the session + await this.ProcessModmailMessageToGuild( + message, + modmails.filter((x: InstanceType)=> x.usersnowflake?.toString() === message.author.id)[0] + ); + + } + + /** + * Processes a modmail message and sends it to the corresponding guild. + * This method is called when an active modmail is found for the user. + * @param {Message} message - The message object to process. + * @param {any} modmail - The modmail object associated with the user. + */ + private async ProcessModmailMessageToGuild(this: ModmailMessageService, message: Message, modmail: InstanceType) { + + // Get the active modmail webhook + const webhook = await Arc3.Arc3.clientInstance.fetchWebhook(modmail.webhooksnowflake?.toString() ?? "0"); + const { text } = useTextContent(Locale.EN).actions; + + SendAttachmentsAndMessageToWebhook(message, webhook) + .then(async (webhookMessageID) => { + + if (webhookMessageID) + this.modmailRepo.addRecentMessage(message.id, webhookMessageID) + + await message.react(text('arc.modmail.delivery.emoji.delivered')); + await this.modmailRepo.CreateTranscript( + modmail, + message.author.id, + message.attachments.map(x => x.proxyURL ), + message.createdAt, + message.content, + message.guildId?? "0", + "Modmail", + false, + message.id + ); + + }).catch(async (e) => { + this.logger.error(e, "Error sending message in modmail: " + modmail._id?.toString()) + await message.react(text('arc.modmail.delivery.emoji.failed')); + }); + + } + + /** + * Processes a modmail message from moderator to user. + * @param {Message} message - The message object from the modmail channel. + * @param {InstanceType} modmail - The modmail object. + */ + private async ProcessModmailMessageToUser(this: ModmailMessageService, message: Message, modmail: InstanceType) { + + const user = await Arc3.Arc3.clientInstance.users.fetch(modmail.usersnowflake?.toString() ?? "0"); + const { text } = useTextContent(Locale.EN).actions; + + SendAttachmentsAndMessageToUser(message, user) + .then( async ( userMessageId ) => { + + if (userMessageId) + this.modmailRepo.addRecentMessage(message.id, userMessageId); + + await message.react(text('arc.modmail.delivery.emoji.delivered')); + await this.modmailRepo.CreateTranscript( + modmail, + message.author.id, + message.attachments.map(x => x.proxyURL), + message.createdAt, + message.content, + message.guildId ?? "0", + "Modmail", + false, + message.id + ); + + }).catch( async (e) => { + this.logger.error(e, "Error sending message to user in modmail: " + modmail._id?.toString()); + await message.react(text('arc.modmail.delivery.emoji.failed')); + }); + + } + + /** + * Handles a comment message in modmail (messages starting with #). + * Comments are logged but not sent to the user. + * @param {Message} message - The message object. + * @param {InstanceType} modmail - The modmail object. + */ + private async HandleModmailComment(this: ModmailMessageService, message: Message, modmail: InstanceType) { + + await this.modmailRepo.CreateTranscript( + modmail, + message.author.id, + message.attachments.map(x => x.proxyURL), + message.createdAt, + message.content, + message.guildId ?? "0", + "Modmail", + true, + message.id + ); + + this.logger.info("Comment added to modmail %s by %s", modmail._id?.toString(), message.author.tag); + } + + /** + * Handles the creation of a new modmail when no active modmail is found for the user. + * It sends a message to the user with a select menu to choose a server for modmail. + * @param {Message} message - The message object to process. + */ + private async HandleNewModmail(this: ModmailMessageService, message: Message) { + + if (!(message.content.includes("mod") || message.content.includes("mail"))) + return; + + this.logger.info("Creating new Modmail"); + + await SendModmailSelectMenu(message); + + } + +} \ No newline at end of file diff --git a/src/lib/util/DiscordUtils.ts b/src/lib/util/DiscordUtils.ts new file mode 100644 index 0000000..adf5fb7 --- /dev/null +++ b/src/lib/util/DiscordUtils.ts @@ -0,0 +1,19 @@ +import { ChannelType, ChannelWebhookCreateOptions, Guild, GuildChannelCreateOptions, TextChannel, Webhook } from "discord.js"; + + +export async function CreateTextChannel(guild: Guild, name: string, options?: Partial) : Promise { + + return await (guild.channels.create({ + name, + type: ChannelType.GuildText, + ...options + }) as Promise); + +} + +export async function CreateWebhook(channel: TextChannel, name: string, options?: Partial) : Promise { + return await channel.createWebhook({ + name, + ...options, + }) +} diff --git a/src/lib/util/ModmailUtils.ts b/src/lib/util/ModmailUtils.ts new file mode 100644 index 0000000..a66367f --- /dev/null +++ b/src/lib/util/ModmailUtils.ts @@ -0,0 +1,286 @@ +import { Arc3 } from "../arc3.js"; + +import { + ActionRowBuilder, + ChannelType, + ComponentEmojiResolvable, + Guild, + Message, + MessageActionRowComponentBuilder, + MessageComponentInteraction, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, + User, + Webhook +} from "discord.js"; + +import Modmail from "../schema/v1/Modmail.js"; + +import { useGuildConfig } from "../hooks/useGuildConfig.js"; +import { Locale, useTextContent } from "../hooks/useTextContent.js"; +import { CreateTextChannel, CreateWebhook } from "./DiscordUtils.js"; +import { ModmailFailedEmbed, ModmailMenuEmbed, ModmailModeratorMessageEmbed, ModmailTranscriptEmbed } from "../../ui/ModmailUi.js"; + +import mongoose from 'mongoose'; +import mongooseLong from 'mongoose-long' +import { ModmailRepo } from "../repositories/ModmailRepo.js"; +mongooseLong(mongoose); +const { Types: { Long, ObjectId} } = mongoose; + +const logger = Arc3.Arc3.clientLogger.child("ModmailUtils"); + +/** + * Initializes modmail for a user in a guild. + * + * @param clientInstance - The Discord client instance. + * @param guild - The guild where modmail is to be initialized. + * @param user - The user for whom modmail is to be initialized. + * @returns A promise that resolves to true if modmail was successfully initialized, false otherwise. + * + */ +export async function initModmailAsync(guild: Guild, user: User, modmailRepo: ModmailRepo) : Promise | undefined> { + + const { getGuildConfig } = useGuildConfig().actions; + const { text } = useTextContent(Locale.EN).actions; + + const guildConfig = await getGuildConfig(guild.id); + + if (!("modmailchannel" in guildConfig)) { + logger.warn("Guild %s does not have a modmail channel configured. Failed to init modmail", guild.id); + return undefined; + } + + const modmailCategorySnowflake = guildConfig["modmailchannel"]; + const modmailCategory = await guild.channels.fetch(modmailCategorySnowflake, { + cache: false + }); + + if (modmailCategory?.type !== ChannelType.GuildCategory) { + logger.warn("Guild %s modmail channel is not a category. Failed to init modmail", guild.id); + return undefined; + } + + const activeModmails = await modmailRepo.getActiveModmails() + + if (activeModmails.map(x => x.usersnowflake?.toString()).includes(user.id)) { + logger.warn("User %s already has an active modmail. Failed to init modmail", user.id); + return undefined; + } + + const mailChannel = await CreateTextChannel( + guild, + `${text('arc.modmail.channel.name')}-${user.username}`, + { parent: modmailCategory?.id} + ); + + const webhook = await CreateWebhook( + mailChannel, + user.username + ); + + const modmail = await modmailRepo.CreateModmail( + user.id, + mailChannel.id, + webhook.id + ); + + return modmail; + +} + +/** + * Builds a select menu with options for each server that has modmail enabled. + * It fetches the guilds from the client and checks their configurations. + * @returns {Promise} A promise that resolves to an array of select menu options. + */ +export async function BuildModmailSelectMenu() { + + const guilds = await Arc3.Arc3.clientInstance.guilds.cache; + const { buildCache } = useGuildConfig().actions; + const guildConfigs = await buildCache(); + const selectMenuOptions = []; + + for ( const [guildId, guild] of guilds) { + + if (!(guildId in guildConfigs)) + continue; + + if (!("modmailchannel" in guildConfigs[guildId])) + continue; + + const fetchedGuild = await guild.fetch(); + const emojis = await fetchedGuild.emojis.fetch(); + const emoji = emojis.find( x => x.name === "arc_icon"); + + const option = new StringSelectMenuOptionBuilder() + .setEmoji(emoji ? {name: emoji.name, id: emoji.id, animated: emoji.animated} as ComponentEmojiResolvable : {}) + .setDefault(false) + .setLabel(guild.name) + .setValue(guild.id) + .setDescription( + fetchedGuild.description + ? (fetchedGuild.description.length > 90 + ? fetchedGuild.description.substring(0, 90) + "..." + : fetchedGuild.description ) + : "..." + ); + + selectMenuOptions.push(option.toJSON()); + + } + + return selectMenuOptions; +} + +/** + * Sends the message content and attachments to the specified webhook. + * @param message The message to send. + * @param webhook The webhook to send the message to. + */ +export async function SendAttachmentsAndMessageToWebhook(message: Message, webhook: Webhook) { + + if (message.attachments.size > 0) { + message.attachments.forEach(async (attachement) => { + await webhook.send({ + content: attachement.proxyURL, + avatarURL: message.author.avatarURL()?? undefined + }); + }); + } + + if (message.content) { + const webhookMessage = await webhook.send({ + content: message.content, + avatarURL: message.author.avatarURL()?? undefined, + isMessage: true + }); + + return webhookMessage.id; + } + +} + +export async function SendAttachmentsAndMessageToUser(message: Message, user: User) { + + if (message.attachments.size > 0) { + for (const attachment of message.attachments.values()) { + + const embedWithImage = ModmailModeratorMessageEmbed( + message.author.username, + message.author.displayAvatarURL(), + "" + ).setImage(attachment.proxyURL); + + await user.send({ embeds: [embedWithImage] }); + } + } + + if (message.content) { + const embed = ModmailModeratorMessageEmbed( + message.author.username, + message.author.displayAvatarURL(), + message.content + ); + + const sentMessage = await user.send({ embeds: [embed] }); + return sentMessage.id; + } + +} + + +/** + * Sends a modmail select menu to the user. + * @param message The message to send the modmail select menu to.1 + */ +export async function SendModmailSelectMenu(message: Message) { + + const { text } = useTextContent(Locale.EN).actions; + + const selectMenuOptions = await BuildModmailSelectMenu(); + const selectMenuBuilder = new StringSelectMenuBuilder() + .addOptions(selectMenuOptions) + .setCustomId("modmail.select.server"); + const buttonRow = new ActionRowBuilder() + .addComponents(selectMenuBuilder); + + await message.author.send({ + components: [buttonRow], + content: text('arc.modmail.menu.select.placeholder') + }); + +} + +export async function LogModmailTranscript(guild: Guild, userSnowflake: string, savedBySnowflake: string) { + + const { getGuildConfig } = useGuildConfig().actions; + const guildConfig = await getGuildConfig(guild.id); + + if (!("transcriptchannel" in guildConfig)) { + logger.warn("Guild %s does not have a transcript channel configured. Failed to save transcript", guild.id); + return; + } + + const transcriptChannelSnowflake = guildConfig['transcriptchannel']; + const transcriptChannel = await guild.channels.fetch(transcriptChannelSnowflake, { + cache: false + }); + + if (!(transcriptChannel && transcriptChannel.type === ChannelType.GuildText)) { + logger.warn("Guild %s transcript channel is not a text channel or does not exist. Failed to save transcript", guild.id); + return undefined; + } + + const transcriptUrl = 'https://example.com/transcript/' + userSnowflake; // Placeholder URL + + await transcriptChannel.send({ + embeds: [ModmailTranscriptEmbed(userSnowflake, savedBySnowflake, transcriptUrl)] + }); + +} + +export async function TryCleanupModmail(interaction: MessageComponentInteraction, userSnowflake: string, error: any = null, failed: boolean = true, options: Partial>= {}) { + + const recentTimestamp = new Date(); + recentTimestamp.setSeconds(recentTimestamp.getSeconds() - 30); + + const modmails = await Modmail.find({ + usersnowflake: Long.fromString(userSnowflake), + ...options as any, + createdAt: failed? { $gte: recentTimestamp } : { $lte: new Date()} + }); + + const modmail = modmails[0]; + + if (modmail) { + + // Delete the channel (also deletes the webhook) + const channel = await interaction.client.channels.fetch(modmail.channelsnowflake?.toString() ?? "0"); + + if (channel) { + await channel.delete("Failed modmail creation cleanup").catch(e => { + logger.error(e, "Failed to delete modmail channel after failed creation"); + }); + } + + if (failed) { + // Send a message to the user + await interaction.user.send({ + embeds: [ModmailFailedEmbed()] + }).catch(e => { + logger.error(e, "Failed to send modmail failed message to user"); + }); + } + + + // Delete the modmail + await modmail.deleteOne().catch(e => { + logger.error(e, "Failed to delete modmail from database"); + }); + + return + + } + + logger.error(error, "Failed to cleanup modmail"); +} \ No newline at end of file diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 0000000..f0550dc --- /dev/null +++ b/src/locales/en.json @@ -0,0 +1,27 @@ +{ + "arc.blacklist": "You are blacklisted from using that command!", + "arc.bot.name": "ARC", + "arc.command.setconfig.sucess": "Config has been sucessfully set", + "arc.modmail.blacklisted": "You are blacklisted from using modmail", + "arc.modmail.channel.name": "Modmail", + "arc.modmail.delivery.emoji.delivered": "📨", + "arc.modmail.delivery.emoji.failed": "🔴", + "arc.modmail.delivery.emoji.edited": "✏️", + "arc.modmail.delivery.recieved.description": "Your modmail request was recieved! Please wait and a staff member will assist you shortly.", + "arc.modmail.delivery.recieved.footer": "v0.1 Thank you for using ARC", + "arc.modmail.menu.button.ban": "Ban", + "arc.modmail.menu.button.ban.emoji": "🔨", + "arc.modmail.menu.button.ping": "Ping", + "arc.modmail.menu.button.ping.emoji": "📣", + "arc.modmail.menu.button.save": "Save", + "arc.modmail.menu.button.save.emoji": "💾", + "arc.modmail.menu.description": "A modmail session was opened with <@{0}>", + "arc.modmail.menu.footer": "ARC v{0} - Modmail", + "arc.modmail.menu.select.placeholder": "Select a server to modmail: ", + "arc.modmail.menu.title": "Modmail", + "arc.modmail.failed.title": "Modmail Failed", + "arc.modmail.failed.description": "There was an error while trying to open your modmail. Please try again later.", + "arc.modmail.transcript.title": "Modmail Transcript", + "arc.modmail.transcript.description": "**Modmail with:** <@{0}>\n**Saved** **by** <@{2}>\n\n[Transcript]({3})", + "arc.modmail.moderator.image": "Image:" +} diff --git a/src/locales/fr.json b/src/locales/fr.json new file mode 100644 index 0000000..2e83162 --- /dev/null +++ b/src/locales/fr.json @@ -0,0 +1,12 @@ +{ + "arc.bot.name": "ARC", + "arc.blacklist": "Vous êtes sur la liste noire et ne pouvez pas utiliser cette commande !", + "arc.command.setconfig.sucess": "La configuration a été définie avec succès", + "arc.modmail.blacklisted": "Vous êtes sur la liste noire et ne pouvez pas utiliser Modmail", + "arc.modmail.channel.name": "Modmail", + "arc.modmail.delivery.emoji.delivered": "📨", + "arc.modmail.delivery.emoji.failed": "🔴", + "arc.modmail.delivery.recieved.description": "Votre demande de modmail a bien été reçue ! Veuillez patienter, un membre du personnel vous aidera sous peu.", + "arc.modmail.delivery.recieved.footer": "v0.1 Merci d'utiliser ARC", + "arc.modmail.selectmenu.placeholder": "Sélectionnez un serveur pour envoyer un modmail : " +} \ No newline at end of file diff --git a/src/locales/zh.json b/src/locales/zh.json new file mode 100644 index 0000000..82b4e47 --- /dev/null +++ b/src/locales/zh.json @@ -0,0 +1,12 @@ +{ + "arc.bot.name": "ARC", + "arc.blacklist": "您已被列入黑名单,无法使用该命令!", + "arc.command.setconfig.sucess": "配置已成功设置", + "arc.modmail.blacklisted": "您已被列入黑名单,无法使用 Modmail", + "arc.modmail.channel.name": "Modmail", + "arc.modmail.delivery.emoji.delivered": "📨", + "arc.modmail.delivery.emoji.failed": "🔴", + "arc.modmail.delivery.recieved.description": "您的 Modmail 请求已收到!请稍候,工作人员会尽快为您提供帮助。", + "arc.modmail.delivery.recieved.footer": "v0.1 感谢您使用 ARC", + "arc.modmail.selectmenu.placeholder": "选择服务器以发送 Modmail:" +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 493b435..fe3c5f8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,71 +1,10 @@ -import { dirname, importx } from "@discordx/importer"; -import type { Interaction, Message } from "discord.js"; -import { IntentsBitField } from "discord.js"; -import { Client } from "discordx"; +import { Arc3 } from "./lib/arc3.js" +import * as dotenv from 'dotenv'; -export const bot = new Client({ - // To use only guild command - // botGuilds: [(client) => client.guilds.cache.map((guild) => guild.id)], +dotenv.config({ path: '.env'}) - // Discord intents - intents: [ - IntentsBitField.Flags.Guilds, - IntentsBitField.Flags.GuildMembers, - IntentsBitField.Flags.GuildMessages, - IntentsBitField.Flags.GuildMessageReactions, - IntentsBitField.Flags.GuildVoiceStates, - ], +const arc = new Arc3.Arc3(); - // Debug logs are disabled in silent mode - silent: false, - - // Configuration for @SimpleCommand - simpleCommand: { - prefix: "!", - }, -}); - -bot.once("ready", () => { - // Make sure all guilds are cached - // await bot.guilds.fetch(); - - // Synchronize applications commands with Discord - void bot.initApplicationCommands(); - - // To clear all guild commands, uncomment this line, - // This is useful when moving from guild commands to global commands - // It must only be executed once - // - // await bot.clearApplicationCommands( - // ...bot.guilds.cache.map((g) => g.id) - // ); - - console.log("Bot started"); -}); - -bot.on("interactionCreate", (interaction: Interaction) => { - bot.executeInteraction(interaction); +arc.runAsync().finally(() => { + console.log("Running"); }); - -bot.on("messageCreate", (message: Message) => { - void bot.executeCommand(message); -}); - -async function run() { - // The following syntax should be used in the commonjs environment - // - // await importx(__dirname + "/{events,commands}/**/*.{ts,js}"); - - // The following syntax should be used in the ECMAScript environment - await importx(`${dirname(import.meta.url)}/{events,commands}/**/*.{ts,js}`); - - // Let's start the bot - if (!process.env.BOT_TOKEN) { - throw Error("Could not find BOT_TOKEN in your environment"); - } - - // Log in with your bot token - await bot.login(process.env.BOT_TOKEN); -} - -void run(); diff --git a/src/ui/ModmailUi.ts b/src/ui/ModmailUi.ts new file mode 100644 index 0000000..2e392e8 --- /dev/null +++ b/src/ui/ModmailUi.ts @@ -0,0 +1,151 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, MessageActionRowComponentBuilder, User } from "discord.js"; +import { Locale, useTextContent } from "../lib/hooks/useTextContent.js"; +import { Arc3 } from "../lib/arc3.js"; + +export function ModmailSentEmbed() { + + const embedBuilder = new EmbedBuilder(); + const self = Arc3.Arc3.clientInstance.user; + const { text } = useTextContent(Locale.EN).actions; + + if (!self) + throw new Error("Client user is not initialized.") + + embedBuilder.setAuthor({ + name: self.username, + iconURL: self.avatarURL()?? undefined + }); + + embedBuilder.setDescription(text('arc.modmail.delivery.recieved.description')); + + embedBuilder.setFooter({ + text: text('arc.modmail.delivery.recieved.footer'), + iconURL: self.avatarURL()?? undefined, + }); + + embedBuilder.setTimestamp(new Date()); + + return embedBuilder.toJSON(); +} + +export function ModmailMenuEmbed( userId: string, modmailId: string | undefined) { + + const clientUser = Arc3.Arc3.clientInstance.user; + const embedBuilder = new EmbedBuilder(); + const { text } = useTextContent(Locale.EN).actions; + + const embedStrings = { + footer: text('arc.modmail.menu.footer', Arc3.Arc3.clientVersion), + title: text('arc.modmail.menu.title'), + description: text('arc.modmail.menu.description', userId), + buttons: { + save: { + text: text('arc.modmail.menu.button.save'), + emoji: text('arc.modmail.menu.button.save.emoji') + }, + ban: { + text: text('arc.modmail.menu.button.ban'), + emoji: text('arc.modmail.menu.button.ban.emoji') + }, + ping: { + text: text('arc.modmail.menu.button.ping'), + emoji: text('arc.modmail.menu.button.ping.emoji') + } + } + + + } + + embedBuilder.setTimestamp(new Date()); + embedBuilder.setFooter({ + text: embedStrings.footer, + iconURL: clientUser?.avatarURL() ?? undefined + }); + + embedBuilder.setTitle(embedStrings.title); + embedBuilder.setDescription(embedStrings.description); + + const buttonRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`modmail.save.${modmailId}`) + .setStyle(ButtonStyle.Secondary) + .setEmoji(embedStrings.buttons.save.emoji) + .setLabel(embedStrings.buttons.save.text), + new ButtonBuilder() + .setCustomId(`modmail.ban.${modmailId}`) + .setStyle(ButtonStyle.Danger) + .setEmoji(embedStrings.buttons.ban.emoji) + .setLabel(embedStrings.buttons.ban.text), + new ButtonBuilder() + .setCustomId(`modmail.ping.${modmailId}`) + .setStyle(ButtonStyle.Success) + .setEmoji(embedStrings.buttons.ping.emoji) + .setLabel(embedStrings.buttons.ping.text) + ); + + return { + embeds: [embedBuilder.toJSON()], + components: [buttonRow] + } + +} + +export function ModmailFailedEmbed() { + + const { text } = useTextContent(Locale.EN).actions; + + const embedStrings = { + title: text('arc.modmail.failed.title'), + description: text('arc.modmail.failed.description') + } + + return new EmbedBuilder() + .setTitle(embedStrings.title) + .setDescription(embedStrings.description) + .setColor("Red"); +} + +export function ModmailTranscriptEmbed(userSnowflake: string, savedBySnowflake: string, transcriptUrl:string, savedAt: Date = new Date()) { + + const clientUser = Arc3.Arc3.clientInstance.user; + const { text } = useTextContent(Locale.EN).actions; + + const embedStrings = { + title: text('arc.modmail.transcript.title'), + description: text('arc.modmail.transcript.description', userSnowflake, Math.trunc(savedAt.getTime()/1000).toString(), savedBySnowflake, transcriptUrl), + footer: text('arc.modmail.menu.footer', Arc3.Arc3.clientVersion), + } + + return new EmbedBuilder() + .setTitle(embedStrings.title) + .setDescription(embedStrings.description) + .setFooter({ + iconURL: clientUser?.avatarURL() ?? undefined, + text: embedStrings.footer + }); + +} + +export function ModmailModeratorMessageEmbed(authorName: string, authorAvatarUrl: string, content: string) { + + const clientUser = Arc3.Arc3.clientInstance.user; + const { text } = useTextContent(Locale.EN).actions; + + const embedStrings = { + footer: text('arc.modmail.menu.footer', Arc3.Arc3.clientVersion) + } + + return new EmbedBuilder() + .setAuthor({ + name: authorName, + iconURL: authorAvatarUrl + }) + .setDescription(content) + .setFooter({ + iconURL: clientUser?.avatarURL() ?? undefined, + text: embedStrings.footer + }) + .setTimestamp(new Date()) + .setColor(0x5865F2); +} diff --git a/tsconfig.json b/tsconfig.json index f5de8d5..59c7cab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,13 @@ { "compilerOptions": { "target": "ESNext", - "module": "ESNext", + "module": "esnext", "outDir": "build", "rootDir": "src", "strict": true, "moduleResolution": "Node", "allowSyntheticDefaultImports": true, - + "resolveJsonModule": true, "experimentalDecorators": true, "emitDecoratorMetadata": false,