From 9e12065f0db5a8c06d17c5710f0f8b0cf102eefc Mon Sep 17 00:00:00 2001 From: milo Date: Tue, 10 Feb 2026 02:20:24 +0100 Subject: [PATCH 1/5] clean, lint, format --- README.md | 17 +- package-lock.json | 36 +- package.json | 7 +- pnpm-lock.yaml | 3167 +++++++++++++++++ .../migrations/20260209222315/migration.sql | 97 + prisma/migrations/migration_lock.toml | 3 + src/bot/commands/timeout.js | 28 +- src/bot/components/pollVote.js | 12 +- src/bot/handlers/messageCreate.js | 18 +- src/game/elo.js | 64 +- src/server/routes/api.js | 95 +- src/server/routes/blackjack.js | 18 +- src/server/routes/monke.js | 16 +- src/server/routes/poker.js | 5 +- src/server/socket.js | 8 +- src/services/game.service.js | 4 +- src/services/market.service.js | 20 +- src/services/solitaire.service.js | 3 +- src/services/user.service.js | 2 + src/utils/caseOpening.js | 46 +- src/utils/index.js | 111 +- 21 files changed, 3587 insertions(+), 190 deletions(-) create mode 100644 pnpm-lock.yaml create mode 100644 prisma/migrations/20260209222315/migration.sql create mode 100644 prisma/migrations/migration_lock.toml diff --git a/README.md b/README.md index fab2160..df37c21 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ ``` ├── public/ │ └── images/ # Static assets -├── src/ +├── src/ │ ├── api/ # External API integrations -│ ├── bot/ +│ ├── bot/ │ │ ├── commands/ # Slash command implementations │ │ ├── components/ # Discord message components │ │ ├── handlers/ # Event handlers @@ -17,10 +17,10 @@ │ │ └── events.js # Event registration │ ├── config/ │ │ └── commands.js # Slash command definitions -│ ├── database/ +│ ├── database/ │ │ └── index.js # Database connection and models │ ├── game/ # Game logic and data -│ ├── server/ +│ ├── server/ │ │ ├── routes/ # Express routes │ │ ├── app.js # Express app setup │ │ └── socket.js # Socket.io setup @@ -30,6 +30,7 @@ ``` ## Features + - **Moderation Tools** : Includes commands for managing server members. - **AI Integration** : Utilizes AI APIs for enhanced interactions. - **Game Mechanics** : Implements game features and logic. @@ -38,12 +39,14 @@ - **Web Integration** : Designed to work alongside a [FlopoSite](https://floposite.com) (see [FlopoSite's repo)](https://github.com/cassoule/floposite)). ## Additional Information -Note that FlopoBot is a work in progress, and new features and improvements are continually being added. Contributions and feedback are welcome ! -FlopoBot was orriginally created to be integrated in a specific Discord server, so adding it to other servers won't provide the full experience (for now). +Note that FlopoBot is a work in progress, and new features and improvements are continually being added. Contributions and feedback are welcome ! + +FlopoBot was orriginally created to be integrated in a specific Discord server, so adding it to other servers won't provide the full experience (for now). FlopoSite though is public and can be used by anyone :) ## Related Links + - [FlopoSite Website](https://floposite.com) -- [FlopoSite Repository](https://github.com/cassoule/floposite) \ No newline at end of file +- [FlopoSite Repository](https://github.com/cassoule/floposite) diff --git a/package-lock.json b/package-lock.json index 907a8f7..9aeef9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@prisma/client": "^6.19.2", "axios": "^1.9.0", "discord-interactions": "^4.0.0", - "discord.js": "^14.18.0", + "discord.js": "^14.25.1", "dotenv": "^16.0.3", "express": "^4.18.2", "node-cron": "^3.0.3", @@ -700,9 +700,9 @@ } }, "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", - "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", "license": "MIT", "engines": { "node": ">=v14.0.0", @@ -856,13 +856,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -2070,9 +2070,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -2106,9 +2106,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -2813,9 +2813,9 @@ "license": "ISC" }, "node_modules/magic-bytes.js": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", - "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz", + "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==", "license": "MIT" }, "node_modules/math-intrinsics": { diff --git a/package.json b/package.json index 7149f5f..4fd17a9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ "register": "node commands.js", "dev": "nodemon index.js", "prisma:generate": "prisma generate", - "prisma:migrate": "prisma migrate dev" + "prisma:migrate": "prisma migrate dev", + "format": "prettier --write .", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "stripe:whook": "stripe listen --forward-to localhost:3000/api/buy-coins" }, "author": "Milo Gourvest", "license": "MIT", @@ -23,7 +26,7 @@ "@prisma/client": "^6.19.2", "axios": "^1.9.0", "discord-interactions": "^4.0.0", - "discord.js": "^14.18.0", + "discord.js": "^14.25.1", "dotenv": "^16.0.3", "express": "^4.18.2", "node-cron": "^3.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..0c94731 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3167 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + "@google/genai": + specifier: ^1.30.0 + version: 1.40.0 + "@mistralai/mistralai": + specifier: ^1.6.0 + version: 1.14.0 + "@prisma/client": + specifier: ^6.19.2 + version: 6.19.2(prisma@6.19.2) + axios: + specifier: ^1.9.0 + version: 1.13.5 + discord-interactions: + specifier: ^4.0.0 + version: 4.4.0 + discord.js: + specifier: ^14.25.1 + version: 14.25.1 + dotenv: + specifier: ^16.0.3 + version: 16.6.1 + express: + specifier: ^4.18.2 + version: 4.22.1 + node-cron: + specifier: ^3.0.3 + version: 3.0.3 + openai: + specifier: ^4.104.0 + version: 4.104.0(ws@8.19.0)(zod@4.3.6) + pokersolver: + specifier: ^2.1.4 + version: 2.1.4 + prisma: + specifier: ^6.19.2 + version: 6.19.2 + socket.io: + specifier: ^4.8.1 + version: 4.8.3 + stripe: + specifier: ^20.3.0 + version: 20.3.1(@types/node@25.2.2) + unique-names-generator: + specifier: ^4.7.1 + version: 4.7.1 + uuid: + specifier: ^11.1.0 + version: 11.1.0 + devDependencies: + "@eslint/json": + specifier: ^0.14.0 + version: 0.14.0 + eslint: + specifier: ^9.39.1 + version: 9.39.2(jiti@2.6.1) + globals: + specifier: ^16.5.0 + version: 16.5.0 + nodemon: + specifier: ^3.0.0 + version: 3.1.11 + prettier: + specifier: 3.6.2 + version: 3.6.2 + +packages: + "@discordjs/builders@1.13.1": + resolution: + { integrity: sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w== } + engines: { node: ">=16.11.0" } + + "@discordjs/collection@1.5.3": + resolution: + { integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ== } + engines: { node: ">=16.11.0" } + + "@discordjs/collection@2.1.1": + resolution: + { integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg== } + engines: { node: ">=18" } + + "@discordjs/formatters@0.6.2": + resolution: + { integrity: sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ== } + engines: { node: ">=16.11.0" } + + "@discordjs/rest@2.6.0": + resolution: + { integrity: sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w== } + engines: { node: ">=18" } + + "@discordjs/util@1.2.0": + resolution: + { integrity: sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg== } + engines: { node: ">=18" } + + "@discordjs/ws@1.2.3": + resolution: + { integrity: sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw== } + engines: { node: ">=16.11.0" } + + "@eslint-community/eslint-utils@4.9.1": + resolution: + { integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + "@eslint-community/regexpp@4.12.2": + resolution: + { integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + "@eslint/config-array@0.21.1": + resolution: + { integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/config-helpers@0.4.2": + resolution: + { integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/core@0.17.0": + resolution: + { integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/eslintrc@3.3.3": + resolution: + { integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/js@9.39.2": + resolution: + { integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/json@0.14.0": + resolution: + { integrity: sha512-rvR/EZtvUG3p9uqrSmcDJPYSH7atmWr0RnFWN6m917MAPx82+zQgPUmDu0whPFG6XTyM0vB/hR6c1Q63OaYtCQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/object-schema@2.1.7": + resolution: + { integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/plugin-kit@0.4.1": + resolution: + { integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@google/genai@1.40.0": + resolution: + { integrity: sha512-fhIww8smT0QYRX78qWOiz/nIQhHMF5wXOrlXvj33HBrz3vKDBb+wibLcEmTA+L9dmPD4KmfNr7UF3LDQVTXNjA== } + engines: { node: ">=20.0.0" } + peerDependencies: + "@modelcontextprotocol/sdk": ^1.25.2 + peerDependenciesMeta: + "@modelcontextprotocol/sdk": + optional: true + + "@humanfs/core@0.19.1": + resolution: + { integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== } + engines: { node: ">=18.18.0" } + + "@humanfs/node@0.16.7": + resolution: + { integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== } + engines: { node: ">=18.18.0" } + + "@humanwhocodes/module-importer@1.0.1": + resolution: + { integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== } + engines: { node: ">=12.22" } + + "@humanwhocodes/momoa@3.3.10": + resolution: + { integrity: sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ== } + engines: { node: ">=18" } + + "@humanwhocodes/retry@0.4.3": + resolution: + { integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== } + engines: { node: ">=18.18" } + + "@isaacs/cliui@8.0.2": + resolution: + { integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== } + engines: { node: ">=12" } + + "@mistralai/mistralai@1.14.0": + resolution: + { integrity: sha512-6zaj2f2LCd37cRpBvCgctkDbXtYBlAC85p+u4uU/726zjtsI+sdVH34qRzkm9iE3tRb8BoaiI0/P7TD+uMvLLQ== } + + "@pkgjs/parseargs@0.11.0": + resolution: + { integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== } + engines: { node: ">=14" } + + "@prisma/client@6.19.2": + resolution: + { integrity: sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg== } + engines: { node: ">=18.18" } + peerDependencies: + prisma: "*" + typescript: ">=5.1.0" + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + "@prisma/config@6.19.2": + resolution: + { integrity: sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ== } + + "@prisma/debug@6.19.2": + resolution: + { integrity: sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A== } + + "@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7": + resolution: + { integrity: sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA== } + + "@prisma/engines@6.19.2": + resolution: + { integrity: sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A== } + + "@prisma/fetch-engine@6.19.2": + resolution: + { integrity: sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q== } + + "@prisma/get-platform@6.19.2": + resolution: + { integrity: sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA== } + + "@protobufjs/aspromise@1.1.2": + resolution: + { integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== } + + "@protobufjs/base64@1.1.2": + resolution: + { integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== } + + "@protobufjs/codegen@2.0.4": + resolution: + { integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== } + + "@protobufjs/eventemitter@1.1.0": + resolution: + { integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== } + + "@protobufjs/fetch@1.1.0": + resolution: + { integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== } + + "@protobufjs/float@1.0.2": + resolution: + { integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== } + + "@protobufjs/inquire@1.1.0": + resolution: + { integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== } + + "@protobufjs/path@1.1.2": + resolution: + { integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== } + + "@protobufjs/pool@1.1.0": + resolution: + { integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== } + + "@protobufjs/utf8@1.1.0": + resolution: + { integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== } + + "@sapphire/async-queue@1.5.5": + resolution: + { integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg== } + engines: { node: ">=v14.0.0", npm: ">=7.0.0" } + + "@sapphire/shapeshift@4.0.0": + resolution: + { integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg== } + engines: { node: ">=v16" } + + "@sapphire/snowflake@3.5.3": + resolution: + { integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ== } + engines: { node: ">=v14.0.0", npm: ">=7.0.0" } + + "@socket.io/component-emitter@3.1.2": + resolution: + { integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== } + + "@standard-schema/spec@1.1.0": + resolution: + { integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== } + + "@types/cors@2.8.19": + resolution: + { integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== } + + "@types/estree@1.0.8": + resolution: + { integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== } + + "@types/json-schema@7.0.15": + resolution: + { integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } + + "@types/node-fetch@2.6.13": + resolution: + { integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw== } + + "@types/node@18.19.130": + resolution: + { integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg== } + + "@types/node@25.2.2": + resolution: + { integrity: sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ== } + + "@types/ws@8.18.1": + resolution: + { integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== } + + "@vladfrangu/async_event_emitter@2.4.7": + resolution: + { integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g== } + engines: { node: ">=v14.0.0", npm: ">=7.0.0" } + + abort-controller@3.0.0: + resolution: + { integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== } + engines: { node: ">=6.5" } + + accepts@1.3.8: + resolution: + { integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== } + engines: { node: ">= 0.6" } + + acorn-jsx@5.3.2: + resolution: + { integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: + { integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== } + engines: { node: ">=0.4.0" } + hasBin: true + + agent-base@7.1.4: + resolution: + { integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== } + engines: { node: ">= 14" } + + agentkeepalive@4.6.0: + resolution: + { integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== } + engines: { node: ">= 8.0.0" } + + ajv@6.12.6: + resolution: + { integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== } + + ansi-regex@5.0.1: + resolution: + { integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== } + engines: { node: ">=8" } + + ansi-regex@6.2.2: + resolution: + { integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== } + engines: { node: ">=12" } + + ansi-styles@4.3.0: + resolution: + { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } + engines: { node: ">=8" } + + ansi-styles@6.2.3: + resolution: + { integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== } + engines: { node: ">=12" } + + anymatch@3.1.3: + resolution: + { integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== } + engines: { node: ">= 8" } + + argparse@2.0.1: + resolution: + { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } + + array-flatten@1.1.1: + resolution: + { integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== } + + asynckit@0.4.0: + resolution: + { integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== } + + axios@1.13.5: + resolution: + { integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q== } + + balanced-match@1.0.2: + resolution: + { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } + + base64-js@1.5.1: + resolution: + { integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== } + + base64id@2.0.0: + resolution: + { integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== } + engines: { node: ^4.5.0 || >= 5.9 } + + bignumber.js@9.3.1: + resolution: + { integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ== } + + binary-extensions@2.3.0: + resolution: + { integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== } + engines: { node: ">=8" } + + body-parser@1.20.4: + resolution: + { integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== } + engines: { node: ">= 0.8", npm: 1.2.8000 || >= 1.4.16 } + + 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" } + + buffer-equal-constant-time@1.0.1: + resolution: + { integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== } + + bytes@3.1.2: + resolution: + { integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== } + engines: { node: ">= 0.8" } + + c12@3.1.0: + resolution: + { integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw== } + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + call-bind-apply-helpers@1.0.2: + resolution: + { integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== } + engines: { node: ">= 0.4" } + + call-bound@1.0.4: + resolution: + { integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== } + engines: { node: ">= 0.4" } + + callsites@3.1.0: + resolution: + { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } + engines: { node: ">=6" } + + chalk@4.1.2: + resolution: + { integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== } + engines: { node: ">=10" } + + chokidar@3.6.0: + resolution: + { integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== } + engines: { node: ">= 8.10.0" } + + chokidar@4.0.3: + resolution: + { integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== } + engines: { node: ">= 14.16.0" } + + citty@0.1.6: + resolution: + { integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ== } + + citty@0.2.0: + resolution: + { integrity: sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA== } + + color-convert@2.0.1: + resolution: + { integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== } + engines: { node: ">=7.0.0" } + + color-name@1.1.4: + resolution: + { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } + + combined-stream@1.0.8: + resolution: + { integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== } + engines: { node: ">= 0.8" } + + concat-map@0.0.1: + resolution: + { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } + + confbox@0.2.4: + resolution: + { integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ== } + + consola@3.4.2: + resolution: + { integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== } + engines: { node: ^14.18.0 || >=16.10.0 } + + content-disposition@0.5.4: + resolution: + { integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== } + engines: { node: ">= 0.6" } + + content-type@1.0.5: + resolution: + { integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== } + engines: { node: ">= 0.6" } + + cookie-signature@1.0.7: + resolution: + { integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== } + + cookie@0.7.2: + resolution: + { integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== } + engines: { node: ">= 0.6" } + + cors@2.8.6: + resolution: + { integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== } + engines: { node: ">= 0.10" } + + cross-spawn@7.0.6: + resolution: + { integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== } + engines: { node: ">= 8" } + + data-uri-to-buffer@4.0.1: + resolution: + { integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== } + engines: { node: ">= 12" } + + debug@2.6.9: + resolution: + { integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: + { integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: + { integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== } + + deepmerge-ts@7.1.5: + resolution: + { integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw== } + engines: { node: ">=16.0.0" } + + defu@6.1.4: + resolution: + { integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== } + + delayed-stream@1.0.0: + resolution: + { integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== } + engines: { node: ">=0.4.0" } + + depd@2.0.0: + resolution: + { integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== } + engines: { node: ">= 0.8" } + + destr@2.0.5: + resolution: + { integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== } + + destroy@1.2.0: + resolution: + { integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== } + engines: { node: ">= 0.8", npm: 1.2.8000 || >= 1.4.16 } + + discord-api-types@0.38.38: + resolution: + { integrity: sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q== } + + discord-interactions@4.4.0: + resolution: + { integrity: sha512-jjJx8iwAeJcj8oEauV43fue9lNqkf38fy60aSs2+G8D1nJmDxUIrk08o3h0F3wgwuBWWJUZO+X/VgfXsxpCiJA== } + engines: { node: ">=18.4.0" } + + discord.js@14.25.1: + resolution: + { integrity: sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g== } + engines: { node: ">=18" } + + dotenv@16.6.1: + resolution: + { integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== } + engines: { node: ">=12" } + + dunder-proto@1.0.1: + resolution: + { integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== } + engines: { node: ">= 0.4" } + + eastasianwidth@0.2.0: + resolution: + { integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== } + + ecdsa-sig-formatter@1.0.11: + resolution: + { integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== } + + ee-first@1.1.1: + resolution: + { integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== } + + effect@3.18.4: + resolution: + { integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA== } + + emoji-regex@8.0.0: + resolution: + { integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== } + + emoji-regex@9.2.2: + resolution: + { integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== } + + empathic@2.0.0: + resolution: + { integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA== } + engines: { node: ">=14" } + + encodeurl@2.0.0: + resolution: + { integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== } + engines: { node: ">= 0.8" } + + engine.io-parser@5.2.3: + resolution: + { integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== } + engines: { node: ">=10.0.0" } + + engine.io@6.6.5: + resolution: + { integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A== } + engines: { node: ">=10.2.0" } + + es-define-property@1.0.1: + resolution: + { integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== } + engines: { node: ">= 0.4" } + + es-errors@1.3.0: + resolution: + { integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== } + engines: { node: ">= 0.4" } + + es-object-atoms@1.1.1: + resolution: + { integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== } + engines: { node: ">= 0.4" } + + es-set-tostringtag@2.1.0: + resolution: + { integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== } + engines: { node: ">= 0.4" } + + escape-html@1.0.3: + resolution: + { integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== } + + escape-string-regexp@4.0.0: + resolution: + { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } + engines: { node: ">=10" } + + eslint-scope@8.4.0: + resolution: + { integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@3.4.3: + resolution: + { integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.1: + resolution: + { integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint@9.39.2: + resolution: + { integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + hasBin: true + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: + { integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + esquery@1.7.0: + resolution: + { integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== } + engines: { node: ">=0.10" } + + esrecurse@4.3.0: + resolution: + { integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== } + engines: { node: ">=4.0" } + + estraverse@5.3.0: + resolution: + { integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== } + engines: { node: ">=4.0" } + + esutils@2.0.3: + resolution: + { integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== } + engines: { node: ">=0.10.0" } + + etag@1.8.1: + resolution: + { integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== } + engines: { node: ">= 0.6" } + + event-target-shim@5.0.1: + resolution: + { integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== } + engines: { node: ">=6" } + + express@4.22.1: + resolution: + { integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== } + engines: { node: ">= 0.10.0" } + + exsolve@1.0.8: + resolution: + { integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA== } + + extend@3.0.2: + resolution: + { integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== } + + fast-check@3.23.2: + resolution: + { integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A== } + engines: { node: ">=8.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-levenshtein@2.0.6: + resolution: + { integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== } + + fetch-blob@3.2.0: + resolution: + { integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== } + engines: { node: ^12.20 || >= 14.13 } + + file-entry-cache@8.0.0: + resolution: + { integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== } + engines: { node: ">=16.0.0" } + + fill-range@7.1.1: + resolution: + { integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== } + engines: { node: ">=8" } + + finalhandler@1.3.2: + resolution: + { integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== } + engines: { node: ">= 0.8" } + + find-up@5.0.0: + resolution: + { integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== } + engines: { node: ">=10" } + + flat-cache@4.0.1: + resolution: + { integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== } + engines: { node: ">=16" } + + flatted@3.3.3: + resolution: + { integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== } + + follow-redirects@1.15.11: + resolution: + { integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== } + engines: { node: ">=4.0" } + peerDependencies: + debug: "*" + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.1: + resolution: + { integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== } + engines: { node: ">=14" } + + form-data-encoder@1.7.2: + resolution: + { integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== } + + form-data@4.0.5: + resolution: + { integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== } + engines: { node: ">= 6" } + + formdata-node@4.4.1: + resolution: + { integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== } + engines: { node: ">= 12.20" } + + formdata-polyfill@4.0.10: + resolution: + { integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== } + engines: { node: ">=12.20.0" } + + forwarded@0.2.0: + resolution: + { integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== } + engines: { node: ">= 0.6" } + + fresh@0.5.2: + resolution: + { integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== } + engines: { node: ">= 0.6" } + + fsevents@2.3.3: + resolution: + { integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } + + gaxios@7.1.3: + resolution: + { integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ== } + engines: { node: ">=18" } + + gcp-metadata@8.1.2: + resolution: + { integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg== } + engines: { node: ">=18" } + + get-intrinsic@1.3.0: + resolution: + { integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== } + engines: { node: ">= 0.4" } + + get-proto@1.0.1: + resolution: + { integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== } + engines: { node: ">= 0.4" } + + giget@2.0.0: + resolution: + { integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA== } + hasBin: true + + glob-parent@5.1.2: + resolution: + { integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== } + engines: { node: ">= 6" } + + glob-parent@6.0.2: + resolution: + { integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== } + engines: { node: ">=10.13.0" } + + glob@10.5.0: + resolution: + { integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== } + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + globals@14.0.0: + resolution: + { integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== } + engines: { node: ">=18" } + + globals@16.5.0: + resolution: + { integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ== } + engines: { node: ">=18" } + + google-auth-library@10.5.0: + resolution: + { integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w== } + engines: { node: ">=18" } + + google-logging-utils@1.1.3: + resolution: + { integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA== } + engines: { node: ">=14" } + + gopd@1.2.0: + resolution: + { integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== } + engines: { node: ">= 0.4" } + + gtoken@8.0.0: + resolution: + { integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw== } + engines: { node: ">=18" } + + 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" } + + has-symbols@1.1.0: + resolution: + { integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== } + engines: { node: ">= 0.4" } + + has-tostringtag@1.0.2: + resolution: + { integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== } + engines: { node: ">= 0.4" } + + hasown@2.0.2: + resolution: + { integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== } + engines: { node: ">= 0.4" } + + http-errors@2.0.1: + resolution: + { integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== } + engines: { node: ">= 0.8" } + + https-proxy-agent@7.0.6: + resolution: + { integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== } + engines: { node: ">= 14" } + + humanize-ms@1.2.1: + resolution: + { integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== } + + iconv-lite@0.4.24: + resolution: + { integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== } + engines: { node: ">=0.10.0" } + + ignore-by-default@1.0.1: + resolution: + { integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== } + + ignore@5.3.2: + resolution: + { integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== } + engines: { node: ">= 4" } + + import-fresh@3.3.1: + resolution: + { integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== } + engines: { node: ">=6" } + + imurmurhash@0.1.4: + resolution: + { integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== } + engines: { node: ">=0.8.19" } + + inherits@2.0.4: + resolution: + { integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== } + + ipaddr.js@1.9.1: + resolution: + { integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== } + engines: { node: ">= 0.10" } + + is-binary-path@2.1.0: + resolution: + { integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== } + engines: { node: ">=8" } + + is-extglob@2.1.1: + resolution: + { integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== } + engines: { node: ">=0.10.0" } + + is-fullwidth-code-point@3.0.0: + resolution: + { integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== } + engines: { node: ">=8" } + + is-glob@4.0.3: + resolution: + { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } + engines: { node: ">=0.10.0" } + + is-number@7.0.0: + resolution: + { integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== } + engines: { node: ">=0.12.0" } + + isexe@2.0.0: + resolution: + { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + + jackspeak@3.4.3: + resolution: + { integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== } + + jiti@2.6.1: + resolution: + { integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== } + hasBin: true + + js-yaml@4.1.1: + resolution: + { integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== } + hasBin: true + + json-bigint@1.0.0: + resolution: + { integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== } + + json-buffer@3.0.1: + resolution: + { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== } + + json-schema-traverse@0.4.1: + resolution: + { integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== } + + jwa@2.0.1: + resolution: + { integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== } + + jws@4.0.1: + resolution: + { integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== } + + keyv@4.5.4: + resolution: + { integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } + + levn@0.4.1: + resolution: + { integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== } + engines: { node: ">= 0.8.0" } + + locate-path@6.0.0: + resolution: + { integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== } + engines: { node: ">=10" } + + lodash.merge@4.6.2: + resolution: + { integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== } + + lodash.snakecase@4.1.1: + resolution: + { integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== } + + lodash@4.17.23: + resolution: + { integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== } + + long@5.3.2: + resolution: + { integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== } + + lru-cache@10.4.3: + resolution: + { integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== } + + magic-bytes.js@1.13.0: + resolution: + { integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg== } + + math-intrinsics@1.1.0: + resolution: + { integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== } + engines: { node: ">= 0.4" } + + media-typer@0.3.0: + resolution: + { integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== } + engines: { node: ">= 0.6" } + + merge-descriptors@1.0.3: + resolution: + { integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== } + + methods@1.1.2: + resolution: + { integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== } + engines: { node: ">= 0.6" } + + mime-db@1.52.0: + resolution: + { integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== } + engines: { node: ">= 0.6" } + + mime-types@2.1.35: + resolution: + { integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== } + engines: { node: ">= 0.6" } + + mime@1.6.0: + resolution: + { integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== } + engines: { node: ">=4" } + hasBin: true + + 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" } + + minipass@7.1.2: + resolution: + { integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== } + engines: { node: ">=16 || 14 >=14.17" } + + ms@2.0.0: + resolution: + { integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== } + + ms@2.1.3: + resolution: + { integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + + natural-compare@1.4.0: + resolution: + { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } + + negotiator@0.6.3: + resolution: + { integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== } + engines: { node: ">= 0.6" } + + node-cron@3.0.3: + resolution: + { integrity: sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A== } + engines: { node: ">=6.0.0" } + + node-domexception@1.0.0: + resolution: + { integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== } + engines: { node: ">=10.5.0" } + deprecated: Use your platform's native DOMException instead + + node-fetch-native@1.6.7: + resolution: + { integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q== } + + node-fetch@2.7.0: + resolution: + { integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@3.3.2: + resolution: + { integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + nodemon@3.1.11: + resolution: + { integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g== } + engines: { node: ">=10" } + hasBin: true + + normalize-path@3.0.0: + resolution: + { integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== } + engines: { node: ">=0.10.0" } + + nypm@0.6.5: + resolution: + { integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ== } + engines: { node: ">=18" } + hasBin: true + + object-assign@4.1.1: + resolution: + { integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== } + engines: { node: ">=0.10.0" } + + object-inspect@1.13.4: + resolution: + { integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== } + engines: { node: ">= 0.4" } + + ohash@2.0.11: + resolution: + { integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ== } + + on-finished@2.4.1: + resolution: + { integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== } + engines: { node: ">= 0.8" } + + openai@4.104.0: + resolution: + { integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA== } + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + optionator@0.9.4: + resolution: + { integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== } + engines: { node: ">= 0.8.0" } + + p-limit@3.1.0: + resolution: + { integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== } + engines: { node: ">=10" } + + p-locate@5.0.0: + resolution: + { integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== } + engines: { node: ">=10" } + + package-json-from-dist@1.0.1: + resolution: + { integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== } + + parent-module@1.0.1: + resolution: + { integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== } + engines: { node: ">=6" } + + parseurl@1.3.3: + resolution: + { integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== } + engines: { node: ">= 0.8" } + + path-exists@4.0.0: + resolution: + { integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== } + engines: { node: ">=8" } + + 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-to-regexp@0.1.12: + resolution: + { integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== } + + pathe@2.0.3: + resolution: + { integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== } + + perfect-debounce@1.0.0: + resolution: + { integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA== } + + picomatch@2.3.1: + resolution: + { integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== } + engines: { node: ">=8.6" } + + pkg-types@2.3.0: + resolution: + { integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== } + + pokersolver@2.1.4: + resolution: + { integrity: sha512-vmgZS+K8H8r1RePQykFM5YyvlKo1v3xVec8FMBjg9N6mR2Tj/n/X415w+lG67FWbrk71D/CADmKFinDgaQlAsw== } + engines: { "0": node >= 4.0.0 } + + prelude-ls@1.2.1: + resolution: + { integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== } + engines: { node: ">= 0.8.0" } + + prettier@3.6.2: + resolution: + { integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== } + engines: { node: ">=14" } + hasBin: true + + prisma@6.19.2: + resolution: + { integrity: sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg== } + engines: { node: ">=18.18" } + hasBin: true + peerDependencies: + typescript: ">=5.1.0" + peerDependenciesMeta: + typescript: + optional: true + + protobufjs@7.5.4: + resolution: + { integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== } + engines: { node: ">=12.0.0" } + + proxy-addr@2.0.7: + resolution: + { integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== } + engines: { node: ">= 0.10" } + + proxy-from-env@1.1.0: + resolution: + { integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== } + + 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@6.1.0: + resolution: + { integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== } + + qs@6.14.1: + resolution: + { integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== } + engines: { node: ">=0.6" } + + range-parser@1.2.1: + resolution: + { integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== } + engines: { node: ">= 0.6" } + + raw-body@2.5.3: + resolution: + { integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== } + engines: { node: ">= 0.8" } + + rc9@2.1.2: + resolution: + { integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg== } + + readdirp@3.6.0: + resolution: + { integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== } + engines: { node: ">=8.10.0" } + + readdirp@4.1.2: + resolution: + { integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== } + engines: { node: ">= 14.18.0" } + + resolve-from@4.0.0: + resolution: + { integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== } + engines: { node: ">=4" } + + rimraf@5.0.10: + resolution: + { integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== } + hasBin: true + + safe-buffer@5.2.1: + resolution: + { integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== } + + safer-buffer@2.1.2: + resolution: + { integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== } + + semver@7.7.4: + resolution: + { integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== } + engines: { node: ">=10" } + hasBin: true + + send@0.19.2: + resolution: + { integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== } + engines: { node: ">= 0.8.0" } + + serve-static@1.16.3: + resolution: + { integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== } + engines: { node: ">= 0.8.0" } + + setprototypeof@1.2.0: + resolution: + { integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== } + + shebang-command@2.0.0: + resolution: + { integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== } + engines: { node: ">=8" } + + shebang-regex@3.0.0: + resolution: + { integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== } + engines: { node: ">=8" } + + side-channel-list@1.0.0: + resolution: + { integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== } + engines: { node: ">= 0.4" } + + side-channel-map@1.0.1: + resolution: + { integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== } + engines: { node: ">= 0.4" } + + side-channel-weakmap@1.0.2: + resolution: + { integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== } + engines: { node: ">= 0.4" } + + side-channel@1.1.0: + resolution: + { integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== } + engines: { node: ">= 0.4" } + + signal-exit@4.1.0: + resolution: + { integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== } + engines: { node: ">=14" } + + simple-update-notifier@2.0.0: + resolution: + { integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== } + engines: { node: ">=10" } + + socket.io-adapter@2.5.6: + resolution: + { integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ== } + + socket.io-parser@4.2.5: + resolution: + { integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ== } + engines: { node: ">=10.0.0" } + + socket.io@4.8.3: + resolution: + { integrity: sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A== } + engines: { node: ">=10.2.0" } + + statuses@2.0.2: + resolution: + { integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== } + engines: { node: ">= 0.8" } + + string-width@4.2.3: + resolution: + { integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== } + engines: { node: ">=8" } + + string-width@5.1.2: + resolution: + { integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== } + engines: { node: ">=12" } + + strip-ansi@6.0.1: + resolution: + { integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== } + engines: { node: ">=8" } + + strip-ansi@7.1.2: + resolution: + { integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== } + engines: { node: ">=12" } + + strip-json-comments@3.1.1: + resolution: + { integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== } + engines: { node: ">=8" } + + stripe@20.3.1: + resolution: + { integrity: sha512-k990yOT5G5rhX3XluRPw5Y8RLdJDW4dzQ29wWT66piHrbnM2KyamJ1dKgPsw4HzGHRWjDiSSdcI2WdxQUPV3aQ== } + engines: { node: ">=16" } + peerDependencies: + "@types/node": ">=16" + peerDependenciesMeta: + "@types/node": + optional: true + + 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" } + + tinyexec@1.0.2: + resolution: + { integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg== } + engines: { node: ">=18" } + + to-regex-range@5.0.1: + resolution: + { integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== } + engines: { node: ">=8.0" } + + toidentifier@1.0.1: + resolution: + { integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== } + engines: { node: ">=0.6" } + + touch@3.1.1: + resolution: + { integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== } + hasBin: true + + tr46@0.0.3: + resolution: + { integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== } + + ts-mixer@6.0.4: + resolution: + { integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== } + + tslib@2.8.1: + resolution: + { integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== } + + type-check@0.4.0: + resolution: + { integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== } + engines: { node: ">= 0.8.0" } + + type-is@1.6.18: + resolution: + { integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== } + engines: { node: ">= 0.6" } + + undefsafe@2.0.5: + resolution: + { integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== } + + undici-types@5.26.5: + resolution: + { integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== } + + undici-types@7.16.0: + resolution: + { integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== } + + undici@6.21.3: + resolution: + { integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw== } + engines: { node: ">=18.17" } + + unique-names-generator@4.7.1: + resolution: + { integrity: sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow== } + engines: { node: ">=8" } + + unpipe@1.0.0: + resolution: + { integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== } + engines: { node: ">= 0.8" } + + uri-js@4.4.1: + resolution: + { integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } + + utils-merge@1.0.1: + resolution: + { integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== } + engines: { node: ">= 0.4.0" } + + uuid@11.1.0: + resolution: + { integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== } + hasBin: true + + uuid@8.3.2: + resolution: + { integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== } + hasBin: true + + vary@1.1.2: + resolution: + { integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== } + engines: { node: ">= 0.8" } + + web-streams-polyfill@3.3.3: + resolution: + { integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== } + engines: { node: ">= 8" } + + web-streams-polyfill@4.0.0-beta.3: + resolution: + { integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== } + engines: { node: ">= 14" } + + webidl-conversions@3.0.1: + resolution: + { integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== } + + whatwg-url@5.0.0: + resolution: + { integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== } + + which@2.0.2: + resolution: + { integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== } + engines: { node: ">= 8" } + hasBin: true + + word-wrap@1.2.5: + resolution: + { integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== } + engines: { node: ">=0.10.0" } + + wrap-ansi@7.0.0: + resolution: + { integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== } + engines: { node: ">=10" } + + wrap-ansi@8.1.0: + resolution: + { integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== } + engines: { node: ">=12" } + + ws@8.18.3: + resolution: + { integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== } + engines: { node: ">=10.0.0" } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.19.0: + resolution: + { integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== } + engines: { node: ">=10.0.0" } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yocto-queue@0.1.0: + resolution: + { integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== } + engines: { node: ">=10" } + + zod-to-json-schema@3.25.1: + resolution: + { integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA== } + peerDependencies: + zod: ^3.25 || ^4 + + zod@4.3.6: + resolution: + { integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg== } + +snapshots: + "@discordjs/builders@1.13.1": + dependencies: + "@discordjs/formatters": 0.6.2 + "@discordjs/util": 1.2.0 + "@sapphire/shapeshift": 4.0.0 + discord-api-types: 0.38.38 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.4 + tslib: 2.8.1 + + "@discordjs/collection@1.5.3": {} + + "@discordjs/collection@2.1.1": {} + + "@discordjs/formatters@0.6.2": + dependencies: + discord-api-types: 0.38.38 + + "@discordjs/rest@2.6.0": + dependencies: + "@discordjs/collection": 2.1.1 + "@discordjs/util": 1.2.0 + "@sapphire/async-queue": 1.5.5 + "@sapphire/snowflake": 3.5.3 + "@vladfrangu/async_event_emitter": 2.4.7 + discord-api-types: 0.38.38 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.21.3 + + "@discordjs/util@1.2.0": + dependencies: + discord-api-types: 0.38.38 + + "@discordjs/ws@1.2.3": + dependencies: + "@discordjs/collection": 2.1.1 + "@discordjs/rest": 2.6.0 + "@discordjs/util": 1.2.0 + "@sapphire/async-queue": 1.5.5 + "@types/ws": 8.18.1 + "@vladfrangu/async_event_emitter": 2.4.7 + discord-api-types: 0.38.38 + tslib: 2.8.1 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + "@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))": + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + "@eslint-community/regexpp@4.12.2": {} + + "@eslint/config-array@0.21.1": + dependencies: + "@eslint/object-schema": 2.1.7 + debug: 4.4.3(supports-color@5.5.0) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + "@eslint/config-helpers@0.4.2": + dependencies: + "@eslint/core": 0.17.0 + + "@eslint/core@0.17.0": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/eslintrc@3.3.3": + dependencies: + ajv: 6.12.6 + debug: 4.4.3(supports-color@5.5.0) + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + "@eslint/js@9.39.2": {} + + "@eslint/json@0.14.0": + dependencies: + "@eslint/core": 0.17.0 + "@eslint/plugin-kit": 0.4.1 + "@humanwhocodes/momoa": 3.3.10 + natural-compare: 1.4.0 + + "@eslint/object-schema@2.1.7": {} + + "@eslint/plugin-kit@0.4.1": + dependencies: + "@eslint/core": 0.17.0 + levn: 0.4.1 + + "@google/genai@1.40.0": + dependencies: + google-auth-library: 10.5.0 + protobufjs: 7.5.4 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + "@humanfs/core@0.19.1": {} + + "@humanfs/node@0.16.7": + dependencies: + "@humanfs/core": 0.19.1 + "@humanwhocodes/retry": 0.4.3 + + "@humanwhocodes/module-importer@1.0.1": {} + + "@humanwhocodes/momoa@3.3.10": {} + + "@humanwhocodes/retry@0.4.3": {} + + "@isaacs/cliui@8.0.2": + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + "@mistralai/mistralai@1.14.0": + dependencies: + ws: 8.19.0 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + "@pkgjs/parseargs@0.11.0": + optional: true + + "@prisma/client@6.19.2(prisma@6.19.2)": + optionalDependencies: + prisma: 6.19.2 + + "@prisma/config@6.19.2": + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.18.4 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + + "@prisma/debug@6.19.2": {} + + "@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7": {} + + "@prisma/engines@6.19.2": + dependencies: + "@prisma/debug": 6.19.2 + "@prisma/engines-version": 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + "@prisma/fetch-engine": 6.19.2 + "@prisma/get-platform": 6.19.2 + + "@prisma/fetch-engine@6.19.2": + dependencies: + "@prisma/debug": 6.19.2 + "@prisma/engines-version": 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 + "@prisma/get-platform": 6.19.2 + + "@prisma/get-platform@6.19.2": + dependencies: + "@prisma/debug": 6.19.2 + + "@protobufjs/aspromise@1.1.2": {} + + "@protobufjs/base64@1.1.2": {} + + "@protobufjs/codegen@2.0.4": {} + + "@protobufjs/eventemitter@1.1.0": {} + + "@protobufjs/fetch@1.1.0": + dependencies: + "@protobufjs/aspromise": 1.1.2 + "@protobufjs/inquire": 1.1.0 + + "@protobufjs/float@1.0.2": {} + + "@protobufjs/inquire@1.1.0": {} + + "@protobufjs/path@1.1.2": {} + + "@protobufjs/pool@1.1.0": {} + + "@protobufjs/utf8@1.1.0": {} + + "@sapphire/async-queue@1.5.5": {} + + "@sapphire/shapeshift@4.0.0": + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.23 + + "@sapphire/snowflake@3.5.3": {} + + "@socket.io/component-emitter@3.1.2": {} + + "@standard-schema/spec@1.1.0": {} + + "@types/cors@2.8.19": + dependencies: + "@types/node": 25.2.2 + + "@types/estree@1.0.8": {} + + "@types/json-schema@7.0.15": {} + + "@types/node-fetch@2.6.13": + dependencies: + "@types/node": 18.19.130 + form-data: 4.0.5 + + "@types/node@18.19.130": + dependencies: + undici-types: 5.26.5 + + "@types/node@25.2.2": + dependencies: + undici-types: 7.16.0 + + "@types/ws@8.18.1": + dependencies: + "@types/node": 25.2.2 + + "@vladfrangu/async_event_emitter@2.4.7": {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + array-flatten@1.1.1: {} + + asynckit@0.4.0: {} + + axios@1.13.5: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + base64id@2.0.0: {} + + bignumber.js@9.3.1: {} + + binary-extensions@2.3.0: {} + + body-parser@1.20.4: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.14.1 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.12: + dependencies: + 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 + + buffer-equal-constant-time@1.0.1: {} + + bytes@3.1.2: {} + + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.4 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + 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 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + citty@0.2.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + concat-map@0.0.1: {} + + confbox@0.2.4: {} + + consola@3.4.2: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@4.0.1: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + deep-is@0.1.4: {} + + deepmerge-ts@7.1.5: {} + + defu@6.1.4: {} + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + destr@2.0.5: {} + + destroy@1.2.0: {} + + discord-api-types@0.38.38: {} + + discord-interactions@4.4.0: {} + + discord.js@14.25.1: + dependencies: + "@discordjs/builders": 1.13.1 + "@discordjs/collection": 1.5.3 + "@discordjs/formatters": 0.6.2 + "@discordjs/rest": 2.6.0 + "@discordjs/util": 1.2.0 + "@discordjs/ws": 1.2.3 + "@sapphire/snowflake": 3.5.3 + discord-api-types: 0.38.38 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.21.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + + effect@3.18.4: + dependencies: + "@standard-schema/spec": 1.1.0 + fast-check: 3.23.2 + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + empathic@2.0.0: {} + + encodeurl@2.0.0: {} + + engine.io-parser@5.2.3: {} + + engine.io@6.6.5: + dependencies: + "@types/cors": 2.8.19 + "@types/node": 25.2.2 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.6 + debug: 4.4.3(supports-color@5.5.0) + engine.io-parser: 5.2.3 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2(jiti@2.6.1): + dependencies: + "@eslint-community/eslint-utils": 4.9.1(eslint@9.39.2(jiti@2.6.1)) + "@eslint-community/regexpp": 4.12.2 + "@eslint/config-array": 0.21.1 + "@eslint/config-helpers": 0.4.2 + "@eslint/core": 0.17.0 + "@eslint/eslintrc": 3.3.3 + "@eslint/js": 9.39.2 + "@eslint/plugin-kit": 0.4.1 + "@humanfs/node": 0.16.7 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@5.5.0) + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + express@4.22.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.4 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.14.1 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + exsolve@1.0.8: {} + + extend@3.0.2: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data-encoder@1.7.2: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gaxios@7.1.3: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + rimraf: 5.0.10 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.1.3 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.5 + pathe: 2.0.3 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + 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 + + globals@14.0.0: {} + + globals@16.5.0: {} + + google-auth-library@10.5.0: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.1.3 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + gtoken: 8.0.0 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + + google-logging-utils@1.1.3: {} + + gopd@1.2.0: {} + + gtoken@8.0.0: + dependencies: + gaxios: 7.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ignore-by-default@1.0.1: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + ipaddr.js@1.9.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-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + "@isaacs/cliui": 8.0.2 + optionalDependencies: + "@pkgjs/parseargs": 0.11.0 + + jiti@2.6.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash.snakecase@4.1.1: {} + + lodash@4.17.23: {} + + long@5.3.2: {} + + lru-cache@10.4.3: {} + + magic-bytes.js@1.13.0: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + node-cron@3.0.3: + dependencies: + uuid: 8.3.2 + + node-domexception@1.0.0: {} + + node-fetch-native@1.6.7: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + nodemon@3.1.11: + dependencies: + chokidar: 3.6.0 + debug: 4.4.3(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.7.4 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + normalize-path@3.0.0: {} + + nypm@0.6.5: + dependencies: + citty: 0.2.0 + pathe: 2.0.3 + tinyexec: 1.0.2 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + ohash@2.0.11: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + openai@4.104.0(ws@8.19.0)(zod@4.3.6): + dependencies: + "@types/node": 18.19.130 + "@types/node-fetch": 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + ws: 8.19.0 + zod: 4.3.6 + transitivePeerDependencies: + - encoding + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@0.1.12: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picomatch@2.3.1: {} + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + pokersolver@2.1.4: {} + + prelude-ls@1.2.1: {} + + prettier@3.6.2: {} + + prisma@6.19.2: + dependencies: + "@prisma/config": 6.19.2 + "@prisma/engines": 6.19.2 + transitivePeerDependencies: + - magicast + + protobufjs@7.5.4: + dependencies: + "@protobufjs/aspromise": 1.1.2 + "@protobufjs/base64": 1.1.2 + "@protobufjs/codegen": 2.0.4 + "@protobufjs/eventemitter": 1.1.0 + "@protobufjs/fetch": 1.1.0 + "@protobufjs/float": 1.0.2 + "@protobufjs/inquire": 1.1.0 + "@protobufjs/path": 1.1.2 + "@protobufjs/pool": 1.1.0 + "@protobufjs/utf8": 1.1.0 + "@types/node": 25.2.2 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@1.1.0: {} + + pstree.remy@1.1.8: {} + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + qs@6.14.1: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + resolve-from@4.0.0: {} + + rimraf@5.0.10: + dependencies: + glob: 10.5.0 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + semver@7.7.4: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.4 + + socket.io-adapter@2.5.6: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.5: + dependencies: + "@socket.io/component-emitter": 3.1.2 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + socket.io@4.8.3: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.6 + debug: 4.4.3(supports-color@5.5.0) + engine.io: 6.6.5 + socket.io-adapter: 2.5.6 + socket.io-parser: 4.2.5 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + statuses@2.0.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-json-comments@3.1.1: {} + + stripe@20.3.1(@types/node@25.2.2): + optionalDependencies: + "@types/node": 25.2.2 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinyexec@1.0.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + touch@3.1.1: {} + + tr46@0.0.3: {} + + ts-mixer@6.0.4: {} + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + undefsafe@2.0.5: {} + + undici-types@5.26.5: {} + + undici-types@7.16.0: {} + + undici@6.21.3: {} + + unique-names-generator@4.7.1: {} + + unpipe@1.0.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + utils-merge@1.0.1: {} + + uuid@11.1.0: {} + + uuid@8.3.2: {} + + vary@1.1.2: {} + + web-streams-polyfill@3.3.3: {} + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + ws@8.18.3: {} + + ws@8.19.0: {} + + yocto-queue@0.1.0: {} + + zod-to-json-schema@3.25.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} diff --git a/prisma/migrations/20260209222315/migration.sql b/prisma/migrations/20260209222315/migration.sql new file mode 100644 index 0000000..b92e911 --- /dev/null +++ b/prisma/migrations/20260209222315/migration.sql @@ -0,0 +1,97 @@ +/* + Warnings: + + - You are about to alter the column `offered_at` on the `bids` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. + - You are about to alter the column `timestamp` on the `games` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. + - You are about to alter the column `created_at` on the `logs` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. + - You are about to alter the column `closing_at` on the `market_offers` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. + - You are about to alter the column `opening_at` on the `market_offers` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. + - You are about to alter the column `posted_at` on the `market_offers` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. + - You are about to alter the column `created_at` on the `transactions` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. + +*/ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_bids" ( + "id" TEXT NOT NULL PRIMARY KEY, + "bidder_id" TEXT NOT NULL, + "market_offer_id" TEXT NOT NULL, + "offer_amount" INTEGER NOT NULL, + "offered_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "bids_bidder_id_fkey" FOREIGN KEY ("bidder_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "bids_market_offer_id_fkey" FOREIGN KEY ("market_offer_id") REFERENCES "market_offers" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_bids" ("bidder_id", "id", "market_offer_id", "offer_amount", "offered_at") SELECT "bidder_id", "id", "market_offer_id", "offer_amount", "offered_at" FROM "bids"; +DROP TABLE "bids"; +ALTER TABLE "new_bids" RENAME TO "bids"; +CREATE TABLE "new_games" ( + "id" TEXT NOT NULL PRIMARY KEY, + "p1" TEXT NOT NULL, + "p2" TEXT, + "p1_score" INTEGER, + "p2_score" INTEGER, + "p1_elo" INTEGER, + "p2_elo" INTEGER, + "p1_new_elo" INTEGER, + "p2_new_elo" INTEGER, + "type" TEXT, + "timestamp" DATETIME, + CONSTRAINT "games_p1_fkey" FOREIGN KEY ("p1") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "games_p2_fkey" FOREIGN KEY ("p2") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_games" ("id", "p1", "p1_elo", "p1_new_elo", "p1_score", "p2", "p2_elo", "p2_new_elo", "p2_score", "timestamp", "type") SELECT "id", "p1", "p1_elo", "p1_new_elo", "p1_score", "p2", "p2_elo", "p2_new_elo", "p2_score", "timestamp", "type" FROM "games"; +DROP TABLE "games"; +ALTER TABLE "new_games" RENAME TO "games"; +CREATE TABLE "new_logs" ( + "id" TEXT NOT NULL PRIMARY KEY, + "user_id" TEXT NOT NULL, + "action" TEXT, + "target_user_id" TEXT, + "coins_amount" INTEGER, + "user_new_amount" INTEGER, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "logs_target_user_id_fkey" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_logs" ("action", "coins_amount", "created_at", "id", "target_user_id", "user_id", "user_new_amount") SELECT "action", "coins_amount", "created_at", "id", "target_user_id", "user_id", "user_new_amount" FROM "logs"; +DROP TABLE "logs"; +ALTER TABLE "new_logs" RENAME TO "logs"; +CREATE TABLE "new_market_offers" ( + "id" TEXT NOT NULL PRIMARY KEY, + "skin_uuid" TEXT NOT NULL, + "seller_id" TEXT NOT NULL, + "starting_price" INTEGER NOT NULL, + "buyout_price" INTEGER, + "final_price" INTEGER, + "status" TEXT NOT NULL, + "posted_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "opening_at" DATETIME NOT NULL, + "closing_at" DATETIME NOT NULL, + "buyer_id" TEXT, + CONSTRAINT "market_offers_skin_uuid_fkey" FOREIGN KEY ("skin_uuid") REFERENCES "skins" ("uuid") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "market_offers_seller_id_fkey" FOREIGN KEY ("seller_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "market_offers_buyer_id_fkey" FOREIGN KEY ("buyer_id") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_market_offers" ("buyer_id", "buyout_price", "closing_at", "final_price", "id", "opening_at", "posted_at", "seller_id", "skin_uuid", "starting_price", "status") SELECT "buyer_id", "buyout_price", "closing_at", "final_price", "id", "opening_at", "posted_at", "seller_id", "skin_uuid", "starting_price", "status" FROM "market_offers"; +DROP TABLE "market_offers"; +ALTER TABLE "new_market_offers" RENAME TO "market_offers"; +CREATE TABLE "new_transactions" ( + "id" TEXT NOT NULL PRIMARY KEY, + "session_id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "coins_amount" INTEGER NOT NULL, + "amount_cents" INTEGER NOT NULL, + "currency" TEXT NOT NULL DEFAULT 'eur', + "customer_email" TEXT, + "customer_name" TEXT, + "payment_status" TEXT NOT NULL, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "transactions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_transactions" ("amount_cents", "coins_amount", "created_at", "currency", "customer_email", "customer_name", "id", "payment_status", "session_id", "user_id") SELECT "amount_cents", "coins_amount", "created_at", "currency", "customer_email", "customer_name", "id", "payment_status", "session_id", "user_id" FROM "transactions"; +DROP TABLE "transactions"; +ALTER TABLE "new_transactions" RENAME TO "transactions"; +CREATE UNIQUE INDEX "transactions_session_id_key" ON "transactions"("session_id"); +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..2a5a444 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "sqlite" diff --git a/src/bot/commands/timeout.js b/src/bot/commands/timeout.js index a1539b2..a3415ca 100644 --- a/src/bot/commands/timeout.js +++ b/src/bot/commands/timeout.js @@ -102,12 +102,14 @@ export async function handleTimeoutCommand(req, res, client) { if (remaining === 0) { clearInterval(countdownInterval); - const votersList = (await Promise.all(poll.voters - .map(async (voterId) => { - const user = await userService.getUser(voterId); - return `- ${user?.globalName || "Utilisateur Inconnu"}`; - }) - )).join("\n"); + const votersList = ( + await Promise.all( + poll.voters.map(async (voterId) => { + const user = await userService.getUser(voterId); + return `- ${user?.globalName || "Utilisateur Inconnu"}`; + }), + ) + ).join("\n"); try { await DiscordRequest(poll.endpoint, { @@ -143,12 +145,14 @@ export async function handleTimeoutCommand(req, res, client) { // --- Periodic Update Logic --- // Update the message every second with the new countdown try { - const votersList = (await Promise.all(poll.voters - .map(async (voterId) => { - const user = await userService.getUser(voterId); - return `- ${user?.globalName || "Utilisateur Inconnu"}`; - }) - )).join("\n"); + const votersList = ( + await Promise.all( + poll.voters.map(async (voterId) => { + const user = await userService.getUser(voterId); + return `- ${user?.globalName || "Utilisateur Inconnu"}`; + }), + ) + ).join("\n"); await DiscordRequest(poll.endpoint, { method: "PATCH", diff --git a/src/bot/components/pollVote.js b/src/bot/components/pollVote.js index 457626d..02857f6 100644 --- a/src/bot/components/pollVote.js +++ b/src/bot/components/pollVote.js @@ -75,10 +75,14 @@ export async function handlePollVote(req, res) { io.emit("poll-update"); // Notify frontend clients of the change - const votersList = (await Promise.all(poll.voters.map(async (vId) => { - const user = await userService.getUser(vId); - return `- ${user?.globalName || "Utilisateur Inconnu"}`; - }))).join("\n"); + const votersList = ( + await Promise.all( + poll.voters.map(async (vId) => { + const user = await userService.getUser(vId); + return `- ${user?.globalName || "Utilisateur Inconnu"}`; + }), + ) + ).join("\n"); // --- 4. Check for Majority --- if (isVotingFor && poll.for >= poll.requiredMajority) { diff --git a/src/bot/handlers/messageCreate.js b/src/bot/handlers/messageCreate.js index 61cde4d..5ff6261 100644 --- a/src/bot/handlers/messageCreate.js +++ b/src/bot/handlers/messageCreate.js @@ -190,22 +190,22 @@ async function handleAdminCommands(message) { switch (command) { case "?sp": - let msgText = "" + let msgText = ""; for (let skinTierRank = 1; skinTierRank <= 4; skinTierRank++) { msgText += `\n--- Tier Rank: ${skinTierRank} ---\n`; let skinMaxLevels = 4; let skinMaxChromas = 4; for (let skinLevel = 1; skinLevel < skinMaxLevels; skinLevel++) { - msgText += (`Levels: ${skinLevel}/${skinMaxLevels}, MaxChromas: ${1}/${skinMaxChromas} - `); - msgText += (`${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).successProb.toFixed(4)}, `); - msgText += (`${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `); - msgText += (`${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`); + msgText += `Levels: ${skinLevel}/${skinMaxLevels}, MaxChromas: ${1}/${skinMaxChromas} - `; + msgText += `${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).successProb.toFixed(4)}, `; + msgText += `${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `; + msgText += `${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`; } for (let skinChroma = 1; skinChroma < skinMaxChromas; skinChroma++) { - msgText += (`Levels: ${skinMaxLevels}/${skinMaxLevels}, MaxChromas: ${skinChroma}/${skinMaxChromas} - `); - msgText += (`${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).successProb.toFixed(4)}, `); - msgText += (`${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `); - msgText += (`${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`); + msgText += `Levels: ${skinMaxLevels}/${skinMaxLevels}, MaxChromas: ${skinChroma}/${skinMaxChromas} - `; + msgText += `${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).successProb.toFixed(4)}, `; + msgText += `${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `; + msgText += `${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`; } message.reply(msgText); msgText = ""; diff --git a/src/game/elo.js b/src/game/elo.js index f69f37c..51a6c65 100644 --- a/src/game/elo.js +++ b/src/game/elo.js @@ -97,35 +97,33 @@ export async function eloHandler(p1Id, p2Id, p1Score, p2Score, type, scores = nu if (scores) { await gameService.insertGame({ - id: `${p1Id}-${p2Id}-${Date.now()}`, - p1: p1Id, - p2: p2Id, - p1Score: scores.p1, - p2Score: scores.p2, - p1Elo: p1CurrentElo, - p2Elo: p2CurrentElo, - p1NewElo: finalP1Elo, - p2NewElo: finalP2Elo, - type: type, - timestamp: Date.now(), - }); + id: `${p1Id}-${p2Id}-${Date.now()}`, + p1: p1Id, + p2: p2Id, + p1Score: scores.p1, + p2Score: scores.p2, + p1Elo: p1CurrentElo, + p2Elo: p2CurrentElo, + p1NewElo: finalP1Elo, + p2NewElo: finalP2Elo, + type: type, + timestamp: Date.now(), + }); } else { await gameService.insertGame({ - id: `${p1Id}-${p2Id}-${Date.now()}`, - p1: p1Id, - p2: p2Id, - p1Score: p1Score, - p2Score: p2Score, - p1Elo: p1CurrentElo, - p2Elo: p2CurrentElo, - p1NewElo: finalP1Elo, - p2NewElo: finalP2Elo, - type: type, - timestamp: Date.now(), - }); + id: `${p1Id}-${p2Id}-${Date.now()}`, + p1: p1Id, + p2: p2Id, + p1Score: p1Score, + p2Score: p2Score, + p1Elo: p1CurrentElo, + p2Elo: p2CurrentElo, + p1NewElo: finalP1Elo, + p2NewElo: finalP2Elo, + type: type, + timestamp: Date.now(), + }); } - - } /** @@ -142,12 +140,14 @@ export async function pokerEloHandler(room) { if (playerIds.length < 2) return; // Not enough players to calculate Elo // Fetch all players' Elo data at once - const dbPlayers = await Promise.all(playerIds.map(async (id) => { - const user = await userService.getUser(id); - const eloData = await gameService.getUserElo(id); - const elo = eloData?.elo || 1000; - return { ...user, elo }; - })); + const dbPlayers = await Promise.all( + playerIds.map(async (id) => { + const user = await userService.getUser(id); + const eloData = await gameService.getUserElo(id); + const elo = eloData?.elo || 1000; + return { ...user, elo }; + }), + ); const winnerIds = new Set(room.winners); const playerCount = dbPlayers.length; diff --git a/src/server/routes/api.js b/src/server/routes/api.js index 663e159..778a170 100644 --- a/src/server/routes/api.js +++ b/src/server/routes/api.js @@ -157,15 +157,15 @@ export function apiRoutes(client, io) { ); const updatedSkin = await skinService.getSkin(result.randomSkinData.uuid); await handleCaseOpening(caseType, userId, result.randomSelectedSkinUuid, client); - - const contentSkins = selectedSkins.map((item) => { + + const contentSkins = selectedSkins.map((item) => { return { ...item, isMelee: isMeleeSkin(item.displayName), isVCT: isVCTSkin(item.displayName), isChampions: isChampionsSkin(item.displayName), vctRegion: getVCTRegion(item.displayName), - } + }; }); res.json({ selectedSkins: contentSkins, @@ -236,9 +236,7 @@ export function apiRoutes(client, io) { try { const skin = await skinService.getSkin(req.params.uuid); const skinData = skins.find((s) => s.uuid === skin.uuid); - if ( - !skinData - ) { + if (!skinData) { return res.status(403).json({ error: "Invalid skin." }); } if (skin.userId !== userId) { @@ -248,7 +246,9 @@ export function apiRoutes(client, io) { const marketOffers = await marketService.getMarketOffersBySkin(skin.uuid); const activeOffers = marketOffers.filter((offer) => offer.status === "pending" || offer.status === "open"); if (activeOffers.length > 0) { - return res.status(403).json({ error: "Impossible de vendre ce skin, une offre FlopoMarket est déjà en cours." }); + return res + .status(403) + .json({ error: "Impossible de vendre ce skin, une offre FlopoMarket est déjà en cours." }); } const commandUser = await userService.getUser(userId); @@ -288,14 +288,14 @@ export function apiRoutes(client, io) { const { successProb, destructionProb, upgradePrice } = getSkinUpgradeProbs(skin, skinData); const segments = [ - { id: 'SUCCEEDED', color: '5865f2', percent: successProb, label: 'Réussie' }, - { id: 'DESTRUCTED', color: 'f26558', percent: destructionProb, label: 'Détruit' }, - { id: 'NONE', color: '18181818', percent: 1 - successProb - destructionProb, label: 'Échec' }, - ] - + { id: "SUCCEEDED", color: "5865f2", percent: successProb, label: "Réussie" }, + { id: "DESTRUCTED", color: "f26558", percent: destructionProb, label: "Détruit" }, + { id: "NONE", color: "18181818", percent: 1 - successProb - destructionProb, label: "Échec" }, + ]; + res.json({ segments, upgradePrice }); } catch (error) { - console.log(error) + console.log(error); res.status(500).json({ error: "Failed to fetch skin upgrade." }); } }); @@ -305,10 +305,7 @@ export function apiRoutes(client, io) { try { const skin = await skinService.getSkin(req.params.uuid); const skinData = skins.find((s) => s.uuid === skin.uuid); - if ( - !skinData || - (skin.currentLvl >= skinData.levels.length && skin.currentChroma >= skinData.chromas.length) - ) { + if (!skinData || (skin.currentLvl >= skinData.levels.length && skin.currentChroma >= skinData.chromas.length)) { return res.status(403).json({ error: "Skin is already maxed out or invalid skin." }); } if (skin.userId !== userId) { @@ -328,7 +325,7 @@ export function apiRoutes(client, io) { if (commandUser.coins < upgradePrice) { return res.status(403).json({ error: `Pas assez de FlopoCoins (${upgradePrice} requis).` }); } - + await logService.insertLog({ id: `${userId}-${Date.now()}`, userId: userId, @@ -341,7 +338,7 @@ export function apiRoutes(client, io) { let succeeded = false; let destructed = false; - + const roll = Math.random(); if (roll < destructionProb) { destructed = true; @@ -363,7 +360,7 @@ export function apiRoutes(client, io) { return parseFloat(result.toFixed(0)); }; skin.currentPrice = calculatePrice(); - + await skinService.updateSkin({ uuid: skin.uuid, userId: skin.userId, @@ -380,8 +377,10 @@ export function apiRoutes(client, io) { currentPrice: null, }); } - - console.log(`${commandUser.username} attempted to upgrade skin ${skin.uuid} - ${succeeded ? "SUCCEEDED" : destructed ? "DESTRUCTED" : "FAILED"}`); + + console.log( + `${commandUser.username} attempted to upgrade skin ${skin.uuid} - ${succeeded ? "SUCCEEDED" : destructed ? "DESTRUCTED" : "FAILED"}`, + ); res.json({ wonId: succeeded ? "SUCCEEDED" : destructed ? "DESTRUCTED" : "NONE" }); } catch (error) { console.error("Error fetching skin upgrade:", error); @@ -470,7 +469,7 @@ export function apiRoutes(client, io) { try { const games = await gameService.getUserGames(req.params.id); const eloHistory = games - .filter((g) => g.type !== 'POKER_ROUND' && g.type !== 'SOTD') + .filter((g) => g.type !== "POKER_ROUND" && g.type !== "SOTD") .filter((game) => game.p2 !== null) .map((game) => (game.p1 === req.params.id ? game.p1NewElo : game.p2NewElo)); eloHistory.splice(0, 0, 1000); @@ -489,7 +488,7 @@ export function apiRoutes(client, io) { offer.skin = await skinService.getSkin(offer.skinUuid); offer.seller = await userService.getUser(offer.sellerId); offer.buyer = offer.buyerId ? await userService.getUser(offer.buyerId) : null; - offer.bids = await marketService.getOfferBids(offer.id) || {}; + offer.bids = (await marketService.getOfferBids(offer.id)) || {}; for (const bid of offer.bids) { bid.bidder = await userService.getUser(bid.bidderId); } @@ -509,7 +508,10 @@ export function apiRoutes(client, io) { router.get("/user/:id/games-history", async (req, res) => { try { - const games = (await gameService.getUserGames(req.params.id)).filter((g) => g.type !== 'POKER_ROUND' && g.type !== 'SOTD').reverse().slice(0, 50); + const games = (await gameService.getUserGames(req.params.id)) + .filter((g) => g.type !== "POKER_ROUND" && g.type !== "SOTD") + .reverse() + .slice(0, 50); res.json({ games }); } catch (err) { res.status(500).json({ error: "Failed to fetch games history." }); @@ -1160,7 +1162,7 @@ export function apiRoutes(client, io) { try { const user = await userService.getUser(discordId); if (!user) return res.status(404).json({ message: "Utilisateur introuvable" }); - const reward = isWin ? score * 2 : score; + const reward = isWin ? score * 2 : score; const newCoins = user.coins + reward; await userService.updateUserCoins(discordId, newCoins); await logService.insertLog({ @@ -1182,20 +1184,19 @@ export function apiRoutes(client, io) { router.post("/queue/leave", async (req, res) => { const { discordId, game, reason } = req.body; if (game === "snake" && (reason === "beforeunload" || reason === "route-leave")) { - const lobby = Object.values(activeSnakeGames).find( (l) => (l.p1.id === discordId || l.p2.id === discordId) && !l.gameOver, ); if (!lobby) return; - + const player = lobby.p1.id === discordId ? lobby.p1 : lobby.p2; const otherPlayer = lobby.p1.id === discordId ? lobby.p2 : lobby.p1; if (player.gameOver === true) return res.status(200).json({ message: "Déjà quitté" }); player.gameOver = true; otherPlayer.win = true; - + lobby.lastmove = Date.now(); - + // Broadcast the updated state to both players await socketEmit("snakegamestate", { lobby: { @@ -1203,7 +1204,7 @@ export function apiRoutes(client, io) { p2: lobby.p2, }, }); - + // Check if game should end if (lobby.p1.gameOver && lobby.p2.gameOver) { // Both players finished - determine winner @@ -1261,11 +1262,11 @@ export function apiRoutes(client, io) { const FLAPI_URL = process.env.DEV_SITE === "true" ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL; const session = await stripe.checkout.sessions.create({ - payment_method_types: ['card'], + payment_method_types: ["card"], line_items: [ { price_data: { - currency: 'eur', + currency: "eur", product_data: { name: offer.label, description: `Achat de ${offer.label} pour FlopoBot`, @@ -1275,7 +1276,7 @@ export function apiRoutes(client, io) { quantity: 1, }, ], - mode: 'payment', + mode: "payment", success_url: `${FLAPI_URL}/payment-success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${FLAPI_URL}/dashboard`, metadata: { @@ -1284,9 +1285,11 @@ export function apiRoutes(client, io) { }, }); - console.log(`[CHECKOUT] New session for user ${userId}: ${session.id}, offer: ${offer.id} (${offer.coins} coins for ${offer.amount_cents} cents)`); + console.log( + `[CHECKOUT] New session for user ${userId}: ${session.id}, offer: ${offer.id} (${offer.coins} coins for ${offer.amount_cents} cents)`, + ); - res.json({ sessionId: session.id }); + res.json({ sessionId: session.id, url: session.url }); } catch (error) { console.error("Error creating checkout session:", error); res.status(500).json({ error: "Failed to create checkout session" }); @@ -1294,7 +1297,7 @@ export function apiRoutes(client, io) { }); router.post("/buy-coins", async (req, res) => { - const sig = req.headers['stripe-signature']; + const sig = req.headers["stripe-signature"]; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; if (!endpointSecret) { @@ -1303,7 +1306,7 @@ export function apiRoutes(client, io) { } let event; - + try { // Verify webhook signature - requires raw body // Note: You need to configure Express to preserve raw body for this route @@ -1315,9 +1318,9 @@ export function apiRoutes(client, io) { } // Handle the event - if (event.type === 'checkout.session.completed') { + if (event.type === "checkout.session.completed") { const session = event.data.object; - + // Extract metadata from the checkout session const commandUserId = session.metadata?.userId; const expectedCoins = parseInt(session.metadata?.coins); @@ -1325,7 +1328,7 @@ export function apiRoutes(client, io) { const currency = session.currency; const customerEmail = session.customer_details?.email; const customerName = session.customer_details?.name; - + // Validate metadata exists if (!commandUserId || !expectedCoins) { console.error("Missing userId or coins in session metadata"); @@ -1333,7 +1336,7 @@ export function apiRoutes(client, io) { } // Verify payment was successful - if (session.payment_status !== 'paid') { + if (session.payment_status !== "paid") { console.error(`Payment not completed for session ${session.id}`); return res.status(400).json({ error: "Payment not completed" }); } @@ -1380,12 +1383,16 @@ export function apiRoutes(client, io) { userNewAmount: newCoins, }); - console.log(`Payment processed: ${commandUserId} purchased ${expectedCoins} coins for ${amountPaid/100} ${currency}`); + console.log( + `Payment processed: ${commandUserId} purchased ${expectedCoins} coins for ${amountPaid / 100} ${currency}`, + ); // Notify user via Discord if possible try { const discordUser = await client.users.fetch(commandUserId); - await discordUser.send(`✅ Votre achat de ${expectedCoins} FlopoCoins a été confirmé ! Merci pour votre soutien !`); + await discordUser.send( + `✅ Votre achat de ${expectedCoins} FlopoCoins a été confirmé ! Merci pour votre soutien !`, + ); } catch (e) { console.log(`Could not DM user ${commandUserId}:`, e.message); } diff --git a/src/server/routes/blackjack.js b/src/server/routes/blackjack.js index b9703ba..04859cd 100644 --- a/src/server/routes/blackjack.js +++ b/src/server/routes/blackjack.js @@ -183,7 +183,11 @@ export function blackjackRoutes(io) { } emitUpdate("player-joined", snapshot(room)); - emitPlayerUpdate({ id: userId, msg: `${user?.globalName || user?.username} a rejoint la table de Blackjack.`, timestamp: Date.now() }); + emitPlayerUpdate({ + id: userId, + msg: `${user?.globalName || user?.username} a rejoint la table de Blackjack.`, + timestamp: Date.now(), + }); return res.status(200).json({ message: "joined" }); }); @@ -225,7 +229,11 @@ export function blackjackRoutes(io) { delete room.players[userId]; emitUpdate("player-left", snapshot(room)); const user = await client.users.fetch(userId); - emitPlayerUpdate({ id: userId, msg: `${user?.globalName || user?.username} a quitté la table de Blackjack.`, timestamp: Date.now() }); + emitPlayerUpdate({ + id: userId, + msg: `${user?.globalName || user?.username} a quitté la table de Blackjack.`, + timestamp: Date.now(), + }); return res.status(200).json({ message: "left" }); } }); @@ -361,7 +369,11 @@ export function blackjackRoutes(io) { for (const userId of Object.keys(room.leavingAfterRound)) { delete room.players[userId]; const user = await client.users.fetch(userId); - emitPlayerUpdate({ id: userId, msg: `${user?.globalName || user?.username} a quitté la table de Blackjack.`, timestamp: Date.now() }); + emitPlayerUpdate({ + id: userId, + msg: `${user?.globalName || user?.username} a quitté la table de Blackjack.`, + timestamp: Date.now(), + }); } // Prepare next round startBetting(room, now); diff --git a/src/server/routes/monke.js b/src/server/routes/monke.js index e208b7a..424f3a7 100644 --- a/src/server/routes/monke.js +++ b/src/server/routes/monke.js @@ -54,7 +54,9 @@ export function monkeRoutes(client, io) { return res.status(500).json({ error: "Failed to update user coins" }); } - monkePaths[userId] = [{ round: 0, choice: null, result: null, bet: initialBet, extractValue: null, timestamp: Date.now() }]; + monkePaths[userId] = [ + { round: 0, choice: null, result: null, bet: initialBet, extractValue: null, timestamp: Date.now() }, + ]; return res.status(200).json({ message: "Monke game started", userGamePath: monkePaths[userId] }); }); @@ -70,7 +72,8 @@ export function monkeRoutes(client, io) { const currentRound = monkePaths[userId].length - 1; if (step !== currentRound) return res.status(400).json({ error: "Invalid step for the current round" }); - if (monkePaths[userId][currentRound].choice !== null) return res.status(400).json({ error: "This round has already been played" }); + if (monkePaths[userId][currentRound].choice !== null) + return res.status(400).json({ error: "This round has already been played" }); const randomLoseChoice = Math.floor(Math.random() * 3); // 0, 1, or 2 if (choice !== randomLoseChoice) { @@ -79,7 +82,14 @@ export function monkeRoutes(client, io) { monkePaths[userId][currentRound].extractValue = Math.round(monkePaths[userId][currentRound].bet * 1.33); monkePaths[userId][currentRound].timestamp = Date.now(); - monkePaths[userId].push({ round: currentRound + 1, choice: null, result: null, bet: monkePaths[userId][currentRound].extractValue, extractValue: null, timestamp: Date.now() }); + monkePaths[userId].push({ + round: currentRound + 1, + choice: null, + result: null, + bet: monkePaths[userId][currentRound].extractValue, + extractValue: null, + timestamp: Date.now(), + }); return res.status(200).json({ message: "Round won", userGamePath: monkePaths[userId], lost: false }); } else { diff --git a/src/server/routes/poker.js b/src/server/routes/poker.js index 9439282..fd217ad 100644 --- a/src/server/routes/poker.js +++ b/src/server/routes/poker.js @@ -132,7 +132,10 @@ export function pokerRoutes(client, io) { if (Object.values(pokerRooms).some((r) => r.players[userId] || r.queue[userId])) { return res.status(403).json({ message: "You are already in a room or queue." }); } - if (!pokerRooms[roomId].fakeMoney && pokerRooms[roomId].minBet > ((await userService.getUser(userId))?.coins ?? 0)) { + if ( + !pokerRooms[roomId].fakeMoney && + pokerRooms[roomId].minBet > ((await userService.getUser(userId))?.coins ?? 0) + ) { return res.status(403).json({ message: "You do not have enough coins to join this room." }); } diff --git a/src/server/socket.js b/src/server/socket.js index 010168d..f07a1fc 100644 --- a/src/server/socket.js +++ b/src/server/socket.js @@ -95,7 +95,7 @@ async function onQueueJoin(client, gameType, playerId) { console.log(`[${title}] Player ${playerId} already in queue, ignoring duplicate join.`); return; } - + if (Object.values(activeGames).some((g) => g.p1.id === playerId || g.p2.id === playerId)) { console.log(`[${title}] Player ${playerId} already in active game, ignoring queue join.`); return; @@ -277,7 +277,7 @@ export async function onGameOver(client, gameType, playerId, winnerId, reason = game.p1.id === winnerId ? 1 : 0, game.p2.id === winnerId ? 1 : 0, title.toUpperCase(), - scores + scores, ); const winnerName = game.p1.id === winnerId ? game.p1.name : game.p2.name; resultText = `Victoire de ${winnerName}`; @@ -382,7 +382,7 @@ async function createGame(client, gameType) { } io.emit(`${gameType}playing`, { allPlayers: Object.values(activeGames) }); - + // For Snake, also emit a specific match notification to the two players if (gameType === "snake") { io.emit("snakematch", { @@ -395,7 +395,7 @@ async function createGame(client, gameType) { }, }); } - + await emitQueueUpdate(client, gameType); } diff --git a/src/services/game.service.js b/src/services/game.service.js index 77fd29e..93af348 100644 --- a/src/services/game.service.js +++ b/src/services/game.service.js @@ -17,9 +17,7 @@ export async function getUsersByElo() { include: { elo: true }, orderBy: { elo: { elo: "desc" } }, }); - return users - .filter((u) => u.elo) - .map((u) => ({ ...u, elo: u.elo?.elo ?? null })); + return users.filter((u) => u.elo).map((u) => ({ ...u, elo: u.elo?.elo ?? null })); } function toGame(game) { diff --git a/src/services/market.service.js b/src/services/market.service.js index fe3aa5a..cd2a606 100644 --- a/src/services/market.service.js +++ b/src/services/market.service.js @@ -40,15 +40,17 @@ export async function getMarketOffersBySkin(skinUuid) { buyer: { select: { username: true, globalName: true } }, }, }); - return offers.map((offer) => toOffer({ - ...offer, - skinName: offer.skin?.displayName, - skinIcon: offer.skin?.displayIcon, - sellerName: offer.seller?.username, - sellerGlobalName: offer.seller?.globalName, - buyerName: offer.buyer?.username ?? null, - buyerGlobalName: offer.buyer?.globalName ?? null, - })); + return offers.map((offer) => + toOffer({ + ...offer, + skinName: offer.skin?.displayName, + skinIcon: offer.skin?.displayIcon, + sellerName: offer.seller?.username, + sellerGlobalName: offer.seller?.globalName, + buyerName: offer.buyer?.username ?? null, + buyerGlobalName: offer.buyer?.globalName ?? null, + }), + ); } export async function insertMarketOffer(data) { diff --git a/src/services/solitaire.service.js b/src/services/solitaire.service.js index 619d7db..b5ebc07 100644 --- a/src/services/solitaire.service.js +++ b/src/services/solitaire.service.js @@ -14,12 +14,11 @@ export async function deleteSOTD() { export async function getAllSOTDStats() { const stats = await prisma.sotdStat.findMany({ - include: { user: { select: { globalName: true } } }, + include: { user: { select: { globalName: true, avatarUrl: true } } }, orderBy: [{ score: "desc" }, { moves: "asc" }, { time: "asc" }], }); return stats.map((s) => ({ ...s, - globalName: s.user?.globalName, })); } diff --git a/src/services/user.service.js b/src/services/user.service.js index 37ba33a..60a31a1 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -1,4 +1,5 @@ import prisma from "../prisma/client.js"; +import { socketEmit } from "../server/socket.js"; export async function getUser(id) { const user = await prisma.user.findUnique({ @@ -36,6 +37,7 @@ export async function updateUser(data) { } export async function updateUserCoins(id, coins) { + await socketEmit("data-updated", { table: "users", action: "update", userId: id, newCoins: coins }); return prisma.user.update({ where: { id }, data: { coins } }); } diff --git a/src/utils/caseOpening.js b/src/utils/caseOpening.js index 17b08c7..6336cc3 100644 --- a/src/utils/caseOpening.js +++ b/src/utils/caseOpening.js @@ -5,10 +5,12 @@ import { isChampionsSkin } from "./index.js"; export async function drawCaseContent(caseType = "standard", poolSize = 100) { if (caseType === "esport") { // Esport case: return all esport skins - try { + try { const dbSkins = await skinService.getAllAvailableSkins(); const esportSkins = []; - for (const s of skins.filter((s) => dbSkins.find((dbSkin) => dbSkin.displayName.includes("Classic (VCT") && dbSkin.uuid === s.uuid))) { + for (const s of skins.filter((s) => + dbSkins.find((dbSkin) => dbSkin.displayName.includes("Classic (VCT") && dbSkin.uuid === s.uuid), + )) { const dbSkin = await skinService.getSkin(s.uuid); esportSkins.push({ ...s, @@ -59,14 +61,14 @@ export async function drawCaseContent(caseType = "standard", poolSize = 100) { .filter((s) => dbSkins.find((dbSkin) => dbSkin.uuid === s.uuid)) .filter((s) => { if (caseType === "ultra") { - return !(s.displayName.toLowerCase().includes("vct") && s.displayName.toLowerCase().includes("classic")) + return !(s.displayName.toLowerCase().includes("vct") && s.displayName.toLowerCase().includes("classic")); } else { return !s.displayName.toLowerCase().includes("vct"); } }) .filter((s) => { if (caseType === "ultra") { - return true + return true; } else { return isChampionsSkin(s.displayName) === false; } @@ -75,7 +77,8 @@ export async function drawCaseContent(caseType = "standard", poolSize = 100) { for (const s of filtered) { const dbSkin = await skinService.getSkin(s.uuid); const weight = tierWeights[s.contentTierUuid] ?? 0; - if (weight > 0) { // <--- CRITICAL: Remove 0 weight skins + if (weight > 0) { + // <--- CRITICAL: Remove 0 weight skins weightedPool.push({ ...s, tierColor: dbSkin?.tierColor, @@ -90,7 +93,7 @@ export async function drawCaseContent(caseType = "standard", poolSize = 100) { const result = []; // 2. Adjust count if the pool is smaller than requested - const actualCount = Math.min(count, list.length) ; + const actualCount = Math.min(count, list.length); for (let i = 0; i < actualCount; i++) { let r = Math.random() * totalWeight; @@ -172,10 +175,19 @@ export async function drawCaseSkin(caseContent) { export function getSkinUpgradeProbs(skin, skinData) { const successProb = - (1 - (((skin.currentChroma + skin.currentLvl + skinData.chromas.length + skinData.levels.length) / 18) * (parseInt(skin.tierRank) / 4)))/1.5; - const destructionProb = ((skin.currentChroma + skinData.levels.length) / (skinData.chromas.length + skinData.levels.length)) * (parseInt(skin.tierRank) / 5) * 0.075; + (1 - + ((skin.currentChroma + skin.currentLvl + skinData.chromas.length + skinData.levels.length) / 18) * + (parseInt(skin.tierRank) / 4)) / + 1.5; + const destructionProb = + ((skin.currentChroma + skinData.levels.length) / (skinData.chromas.length + skinData.levels.length)) * + (parseInt(skin.tierRank) / 5) * + 0.075; const nextLvl = skin.currentLvl < skinData.levels.length ? skin.currentLvl + 1 : skin.currentLvl; - const nextChroma = skin.currentLvl === skinData.levels.length && skin.currentChroma < skinData.chromas.length ? skin.currentChroma + 1 : skin.currentChroma; + const nextChroma = + skin.currentLvl === skinData.levels.length && skin.currentChroma < skinData.chromas.length + ? skin.currentChroma + 1 + : skin.currentChroma; const calculateNextPrice = () => { let result = parseFloat(skin.basePrice); result *= 1 + nextLvl / Math.max(skinData.levels.length, 2); @@ -187,10 +199,18 @@ export function getSkinUpgradeProbs(skin, skinData) { return { successProb, destructionProb, upgradePrice }; } -export function getDummySkinUpgradeProbs(skinLevel, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, skinMaxPrice) { +export function getDummySkinUpgradeProbs( + skinLevel, + skinChroma, + skinTierRank, + skinMaxLevels, + skinMaxChromas, + skinMaxPrice, +) { const successProb = - 1 - (((skinChroma + skinLevel + (skinMaxChromas + skinMaxLevels)) / 18) * (parseInt(skinTierRank) / 4)); - const destructionProb = ((skinChroma + skinMaxLevels) / (skinMaxChromas + skinMaxLevels)) * (parseInt(skinTierRank) / 5) * 0.1; - const upgradePrice = Math.max(Math.floor((parseFloat(skinMaxPrice) * (1 - successProb))), 1); + 1 - ((skinChroma + skinLevel + (skinMaxChromas + skinMaxLevels)) / 18) * (parseInt(skinTierRank) / 4); + const destructionProb = + ((skinChroma + skinMaxLevels) / (skinMaxChromas + skinMaxLevels)) * (parseInt(skinTierRank) / 5) * 0.1; + const upgradePrice = Math.max(Math.floor(parseFloat(skinMaxPrice) * (1 - successProb)), 1); return { successProb, destructionProb, upgradePrice }; } diff --git a/src/utils/index.js b/src/utils/index.js index f6bf51d..fc83048 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -432,10 +432,26 @@ function formatTierText(rank, displayName) { export function isMeleeSkin(skinName) { const name = skinName.toLowerCase(); - return !(name.includes("classic") || name.includes("shorty") || name.includes("frenzy") || name.includes("ghost") || name.includes("sheriff") || name.includes("stinger") || name.includes("spectre") || - name.includes("bucky") || name.includes("judge") || name.includes("bulldog") || name.includes("guardian") || - name.includes("vandal") || name.includes("phantom") || name.includes("marshal") || name.includes("outlaw") || - name.includes("operator") || name.includes("ares") || name.includes("odin")); + return !( + name.includes("classic") || + name.includes("shorty") || + name.includes("frenzy") || + name.includes("ghost") || + name.includes("sheriff") || + name.includes("stinger") || + name.includes("spectre") || + name.includes("bucky") || + name.includes("judge") || + name.includes("bulldog") || + name.includes("guardian") || + name.includes("vandal") || + name.includes("phantom") || + name.includes("marshal") || + name.includes("outlaw") || + name.includes("operator") || + name.includes("ares") || + name.includes("odin") + ); } export function isVCTSkin(skinName) { @@ -444,31 +460,78 @@ export function isVCTSkin(skinName) { } const VCT_TEAMS = { - "vct-am": [ - /x 100t\)$/g, /x c9\)$/g, /x eg\)$/g, /x fur\)$/g, /x krü\)$/g, /x lev\)$/g, /x loud\)$/g, - /x mibr\)$/g, /x sen\)$/g, /x nrg\)$/g, /x g2\)$/g, /x nv\)$/g, /x 2g\)$/g - ], - "vct-emea": [ - /x bbl\)$/g, /x fnc\)$/g, /x fut\)$/g, /x m8\)$/g, /x gx\)$/g, /x kc\)$/g, /x navi\)$/g, - /x th\)$/g, /x tl\)$/g, /x vit\)$/g, /x ulf\)$/g, /x pcf\)$/g, /x koi\)$/g, /x apk\)$/g - ], - "vct-pcf": [ - /x dfm\)$/g, /x drx\)$/g, /x fs\)$/g, /x gen\)$/g, /x ge\)$/g, /x prx\)$/g, /x rrq\)$/g, - /x t1\)$/g, /x ts\)$/g, /x zeta\)$/g, /x vl\)$/g, /x ns\)$/g, /x tln\)$/g, /x boom\)$/g, /x bld\)$/g - ], - "vct-cn": [ - /x ag\)$/g, /x blg\)$/g, /x edg\)$/g, /x fpx\)$/g, /x jdg\)$/g, /x nova\)$/g, /x tec\)$/g, - /x te\)$/g, /x tyl\)$/g, /x wol\)$/g, /x xlg\)$/g, /x xlg\)$/g, /x drg\)$/g - ] + "vct-am": [ + /x 100t\)$/g, + /x c9\)$/g, + /x eg\)$/g, + /x fur\)$/g, + /x krü\)$/g, + /x lev\)$/g, + /x loud\)$/g, + /x mibr\)$/g, + /x sen\)$/g, + /x nrg\)$/g, + /x g2\)$/g, + /x nv\)$/g, + /x 2g\)$/g, + ], + "vct-emea": [ + /x bbl\)$/g, + /x fnc\)$/g, + /x fut\)$/g, + /x m8\)$/g, + /x gx\)$/g, + /x kc\)$/g, + /x navi\)$/g, + /x th\)$/g, + /x tl\)$/g, + /x vit\)$/g, + /x ulf\)$/g, + /x pcf\)$/g, + /x koi\)$/g, + /x apk\)$/g, + ], + "vct-pcf": [ + /x dfm\)$/g, + /x drx\)$/g, + /x fs\)$/g, + /x gen\)$/g, + /x ge\)$/g, + /x prx\)$/g, + /x rrq\)$/g, + /x t1\)$/g, + /x ts\)$/g, + /x zeta\)$/g, + /x vl\)$/g, + /x ns\)$/g, + /x tln\)$/g, + /x boom\)$/g, + /x bld\)$/g, + ], + "vct-cn": [ + /x ag\)$/g, + /x blg\)$/g, + /x edg\)$/g, + /x fpx\)$/g, + /x jdg\)$/g, + /x nova\)$/g, + /x tec\)$/g, + /x te\)$/g, + /x tyl\)$/g, + /x wol\)$/g, + /x xlg\)$/g, + /x xlg\)$/g, + /x drg\)$/g, + ], }; export function getVCTRegion(skinName) { if (!isVCTSkin(skinName)) return null; const name = skinName.toLowerCase().trim(); for (const [region, regexes] of Object.entries(VCT_TEAMS)) { - if (regexes.some(regex => regex.test(name))) { - return region; - } + if (regexes.some((regex) => regex.test(name))) { + return region; + } } return null; } @@ -476,4 +539,4 @@ export function getVCTRegion(skinName) { export function isChampionsSkin(skinName) { const name = skinName.toLowerCase(); return name.includes("champions"); -} \ No newline at end of file +} From 1c432e68bd68aeb75ed10635f4c897fce178fff6 Mon Sep 17 00:00:00 2001 From: milo Date: Tue, 10 Feb 2026 03:13:09 +0100 Subject: [PATCH 2/5] tiny changes --- .../migration.sql | 42 +++---------------- prisma/schema.prisma | 14 +++---- src/server/routes/solitaire.js | 4 +- src/services/market.service.js | 6 +-- 4 files changed, 18 insertions(+), 48 deletions(-) rename prisma/migrations/{20260209222315 => 20260210015517_use_current_timestamp_defaults}/migration.sql (60%) diff --git a/prisma/migrations/20260209222315/migration.sql b/prisma/migrations/20260210015517_use_current_timestamp_defaults/migration.sql similarity index 60% rename from prisma/migrations/20260209222315/migration.sql rename to prisma/migrations/20260210015517_use_current_timestamp_defaults/migration.sql index b92e911..95c8a2e 100644 --- a/prisma/migrations/20260209222315/migration.sql +++ b/prisma/migrations/20260210015517_use_current_timestamp_defaults/migration.sql @@ -1,15 +1,3 @@ -/* - Warnings: - - - You are about to alter the column `offered_at` on the `bids` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. - - You are about to alter the column `timestamp` on the `games` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. - - You are about to alter the column `created_at` on the `logs` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. - - You are about to alter the column `closing_at` on the `market_offers` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. - - You are about to alter the column `opening_at` on the `market_offers` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. - - You are about to alter the column `posted_at` on the `market_offers` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. - - You are about to alter the column `created_at` on the `transactions` table. The data in that column could be lost. The data in that column will be cast from `String` to `DateTime`. - -*/ -- RedefineTables PRAGMA defer_foreign_keys=ON; PRAGMA foreign_keys=OFF; @@ -18,31 +6,13 @@ CREATE TABLE "new_bids" ( "bidder_id" TEXT NOT NULL, "market_offer_id" TEXT NOT NULL, "offer_amount" INTEGER NOT NULL, - "offered_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "offered_at" TEXT DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "bids_bidder_id_fkey" FOREIGN KEY ("bidder_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "bids_market_offer_id_fkey" FOREIGN KEY ("market_offer_id") REFERENCES "market_offers" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); INSERT INTO "new_bids" ("bidder_id", "id", "market_offer_id", "offer_amount", "offered_at") SELECT "bidder_id", "id", "market_offer_id", "offer_amount", "offered_at" FROM "bids"; DROP TABLE "bids"; ALTER TABLE "new_bids" RENAME TO "bids"; -CREATE TABLE "new_games" ( - "id" TEXT NOT NULL PRIMARY KEY, - "p1" TEXT NOT NULL, - "p2" TEXT, - "p1_score" INTEGER, - "p2_score" INTEGER, - "p1_elo" INTEGER, - "p2_elo" INTEGER, - "p1_new_elo" INTEGER, - "p2_new_elo" INTEGER, - "type" TEXT, - "timestamp" DATETIME, - CONSTRAINT "games_p1_fkey" FOREIGN KEY ("p1") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "games_p2_fkey" FOREIGN KEY ("p2") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_games" ("id", "p1", "p1_elo", "p1_new_elo", "p1_score", "p2", "p2_elo", "p2_new_elo", "p2_score", "timestamp", "type") SELECT "id", "p1", "p1_elo", "p1_new_elo", "p1_score", "p2", "p2_elo", "p2_new_elo", "p2_score", "timestamp", "type" FROM "games"; -DROP TABLE "games"; -ALTER TABLE "new_games" RENAME TO "games"; CREATE TABLE "new_logs" ( "id" TEXT NOT NULL PRIMARY KEY, "user_id" TEXT NOT NULL, @@ -50,7 +20,7 @@ CREATE TABLE "new_logs" ( "target_user_id" TEXT, "coins_amount" INTEGER, "user_new_amount" INTEGER, - "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "created_at" TEXT DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "logs_target_user_id_fkey" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE ); @@ -65,9 +35,9 @@ CREATE TABLE "new_market_offers" ( "buyout_price" INTEGER, "final_price" INTEGER, "status" TEXT NOT NULL, - "posted_at" DATETIME DEFAULT CURRENT_TIMESTAMP, - "opening_at" DATETIME NOT NULL, - "closing_at" DATETIME NOT NULL, + "posted_at" TEXT DEFAULT CURRENT_TIMESTAMP, + "opening_at" TEXT NOT NULL, + "closing_at" TEXT NOT NULL, "buyer_id" TEXT, CONSTRAINT "market_offers_skin_uuid_fkey" FOREIGN KEY ("skin_uuid") REFERENCES "skins" ("uuid") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "market_offers_seller_id_fkey" FOREIGN KEY ("seller_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, @@ -86,7 +56,7 @@ CREATE TABLE "new_transactions" ( "customer_email" TEXT, "customer_name" TEXT, "payment_status" TEXT NOT NULL, - "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "created_at" TEXT DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "transactions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); INSERT INTO "new_transactions" ("amount_cents", "coins_amount", "created_at", "currency", "customer_email", "customer_name", "id", "payment_status", "session_id", "user_id") SELECT "amount_cents", "coins_amount", "created_at", "currency", "customer_email", "customer_name", "id", "payment_status", "session_id", "user_id" FROM "transactions"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 85229eb..fac087b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -64,9 +64,9 @@ model MarketOffer { buyoutPrice Int? @map("buyout_price") finalPrice Int? @map("final_price") status String - postedAt DateTime? @default(now()) @map("posted_at") - openingAt DateTime @map("opening_at") - closingAt DateTime @map("closing_at") + postedAt String? @default(dbgenerated("CURRENT_TIMESTAMP")) @map("posted_at") + openingAt String @map("opening_at") + closingAt String @map("closing_at") buyerId String? @map("buyer_id") skin Skin @relation(fields: [skinUuid], references: [uuid]) @@ -82,7 +82,7 @@ model Bid { bidderId String @map("bidder_id") marketOfferId String @map("market_offer_id") offerAmount Int @map("offer_amount") - offeredAt DateTime? @default(now()) @map("offered_at") + offeredAt String? @default(dbgenerated("CURRENT_TIMESTAMP")) @map("offered_at") bidder User @relation(fields: [bidderId], references: [id]) marketOffer MarketOffer @relation(fields: [marketOfferId], references: [id]) @@ -97,7 +97,7 @@ model Log { targetUserId String? @map("target_user_id") coinsAmount Int? @map("coins_amount") userNewAmount Int? @map("user_new_amount") - createdAt DateTime? @default(now()) @map("created_at") + createdAt String? @default(dbgenerated("CURRENT_TIMESTAMP")) @map("created_at") user User @relation("UserLogs", fields: [userId], references: [id]) targetUser User? @relation("TargetUserLogs", fields: [targetUserId], references: [id]) @@ -116,7 +116,7 @@ model Game { p1NewElo Int? @map("p1_new_elo") p2NewElo Int? @map("p2_new_elo") type String? - timestamp DateTime? + timestamp String? player1 User @relation("Player1", fields: [p1], references: [id]) player2 User? @relation("Player2", fields: [p2], references: [id]) @@ -167,7 +167,7 @@ model Transaction { customerEmail String? @map("customer_email") customerName String? @map("customer_name") paymentStatus String @map("payment_status") - createdAt DateTime? @default(now()) @map("created_at") + createdAt String? @default(dbgenerated("CURRENT_TIMESTAMP")) @map("created_at") user User @relation(fields: [userId], references: [id]) diff --git a/src/server/routes/solitaire.js b/src/server/routes/solitaire.js index affcd0a..97a8a55 100644 --- a/src/server/routes/solitaire.js +++ b/src/server/routes/solitaire.js @@ -81,8 +81,8 @@ export function solitaireRoutes(client, io) { router.post("/start/sotd", async (req, res) => { const { userId } = req.body; /*if (!userId || !getUser.get(userId)) { - return res.status(404).json({ error: 'User not found.' }); - }*/ + return res.status(404).json({ error: 'User not found.' }); + }*/ if (activeSolitaireGames[userId]?.isSOTD) { return res.json({ diff --git a/src/services/market.service.js b/src/services/market.service.js index cd2a606..a32e379 100644 --- a/src/services/market.service.js +++ b/src/services/market.service.js @@ -1,7 +1,7 @@ import prisma from "../prisma/client.js"; function toOffer(offer) { - return { ...offer, openingAt: offer.openingAt.getTime(), closingAt: offer.closingAt.getTime() }; + return { ...offer, openingAt: Number(offer.openingAt), closingAt: Number(offer.closingAt) }; } export async function getMarketOffers() { @@ -57,8 +57,8 @@ export async function insertMarketOffer(data) { return prisma.marketOffer.create({ data: { ...data, - openingAt: new Date(data.openingAt), - closingAt: new Date(data.closingAt), + openingAt: String(data.openingAt), + closingAt: String(data.closingAt), }, }); } From 373a4c6edcb04fd0a527bf276d0362df90ccb9d6 Mon Sep 17 00:00:00 2001 From: milo Date: Tue, 10 Feb 2026 18:27:19 +0100 Subject: [PATCH 3/5] prisma migrations fix --- package-lock.json | 20 ++++++ package.json | 1 + .../migration.sql | 67 ------------------- .../migration.sql | 15 ++--- prisma/schema.prisma | 14 ++-- 5 files changed, 35 insertions(+), 82 deletions(-) delete mode 100644 prisma/migrations/20260210015517_use_current_timestamp_defaults/migration.sql rename prisma/migrations/{0_init => 20260210172326_init}/migration.sql (92%) diff --git a/package-lock.json b/package-lock.json index 9aeef9e..26d4997 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "express": "^4.18.2", "node-cron": "^3.0.3", "openai": "^4.104.0", + "pnpm": "^10.29.2", "pokersolver": "^2.1.4", "prisma": "^6.19.2", "socket.io": "^4.8.1", @@ -740,6 +741,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1651,6 +1653,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3323,6 +3326,22 @@ "pathe": "^2.0.3" } }, + "node_modules/pnpm": { + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/pnpm/-/pnpm-10.29.2.tgz", + "integrity": "sha512-vvQ/p1nZH9LaSzGaWg0T73pFu5haPXNCBYRw+dIFGjuoZ05ilnJlRobvlEOtE6gtor657rPgIhyHuBVP/510uA==", + "license": "MIT", + "bin": { + "pnpm": "bin/pnpm.cjs", + "pnpx": "bin/pnpx.cjs" + }, + "engines": { + "node": ">=18.12" + }, + "funding": { + "url": "https://opencollective.com/pnpm" + } + }, "node_modules/pokersolver": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/pokersolver/-/pokersolver-2.1.4.tgz", @@ -3364,6 +3383,7 @@ "integrity": "sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "6.19.2", "@prisma/engines": "6.19.2" diff --git a/package.json b/package.json index 4fd17a9..a009a26 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "express": "^4.18.2", "node-cron": "^3.0.3", "openai": "^4.104.0", + "pnpm": "^10.29.2", "pokersolver": "^2.1.4", "prisma": "^6.19.2", "socket.io": "^4.8.1", diff --git a/prisma/migrations/20260210015517_use_current_timestamp_defaults/migration.sql b/prisma/migrations/20260210015517_use_current_timestamp_defaults/migration.sql deleted file mode 100644 index 95c8a2e..0000000 --- a/prisma/migrations/20260210015517_use_current_timestamp_defaults/migration.sql +++ /dev/null @@ -1,67 +0,0 @@ --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_bids" ( - "id" TEXT NOT NULL PRIMARY KEY, - "bidder_id" TEXT NOT NULL, - "market_offer_id" TEXT NOT NULL, - "offer_amount" INTEGER NOT NULL, - "offered_at" TEXT DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "bids_bidder_id_fkey" FOREIGN KEY ("bidder_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "bids_market_offer_id_fkey" FOREIGN KEY ("market_offer_id") REFERENCES "market_offers" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_bids" ("bidder_id", "id", "market_offer_id", "offer_amount", "offered_at") SELECT "bidder_id", "id", "market_offer_id", "offer_amount", "offered_at" FROM "bids"; -DROP TABLE "bids"; -ALTER TABLE "new_bids" RENAME TO "bids"; -CREATE TABLE "new_logs" ( - "id" TEXT NOT NULL PRIMARY KEY, - "user_id" TEXT NOT NULL, - "action" TEXT, - "target_user_id" TEXT, - "coins_amount" INTEGER, - "user_new_amount" INTEGER, - "created_at" TEXT DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "logs_target_user_id_fkey" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_logs" ("action", "coins_amount", "created_at", "id", "target_user_id", "user_id", "user_new_amount") SELECT "action", "coins_amount", "created_at", "id", "target_user_id", "user_id", "user_new_amount" FROM "logs"; -DROP TABLE "logs"; -ALTER TABLE "new_logs" RENAME TO "logs"; -CREATE TABLE "new_market_offers" ( - "id" TEXT NOT NULL PRIMARY KEY, - "skin_uuid" TEXT NOT NULL, - "seller_id" TEXT NOT NULL, - "starting_price" INTEGER NOT NULL, - "buyout_price" INTEGER, - "final_price" INTEGER, - "status" TEXT NOT NULL, - "posted_at" TEXT DEFAULT CURRENT_TIMESTAMP, - "opening_at" TEXT NOT NULL, - "closing_at" TEXT NOT NULL, - "buyer_id" TEXT, - CONSTRAINT "market_offers_skin_uuid_fkey" FOREIGN KEY ("skin_uuid") REFERENCES "skins" ("uuid") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "market_offers_seller_id_fkey" FOREIGN KEY ("seller_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "market_offers_buyer_id_fkey" FOREIGN KEY ("buyer_id") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_market_offers" ("buyer_id", "buyout_price", "closing_at", "final_price", "id", "opening_at", "posted_at", "seller_id", "skin_uuid", "starting_price", "status") SELECT "buyer_id", "buyout_price", "closing_at", "final_price", "id", "opening_at", "posted_at", "seller_id", "skin_uuid", "starting_price", "status" FROM "market_offers"; -DROP TABLE "market_offers"; -ALTER TABLE "new_market_offers" RENAME TO "market_offers"; -CREATE TABLE "new_transactions" ( - "id" TEXT NOT NULL PRIMARY KEY, - "session_id" TEXT NOT NULL, - "user_id" TEXT NOT NULL, - "coins_amount" INTEGER NOT NULL, - "amount_cents" INTEGER NOT NULL, - "currency" TEXT NOT NULL DEFAULT 'eur', - "customer_email" TEXT, - "customer_name" TEXT, - "payment_status" TEXT NOT NULL, - "created_at" TEXT DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "transactions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_transactions" ("amount_cents", "coins_amount", "created_at", "currency", "customer_email", "customer_name", "id", "payment_status", "session_id", "user_id") SELECT "amount_cents", "coins_amount", "created_at", "currency", "customer_email", "customer_name", "id", "payment_status", "session_id", "user_id" FROM "transactions"; -DROP TABLE "transactions"; -ALTER TABLE "new_transactions" RENAME TO "transactions"; -CREATE UNIQUE INDEX "transactions_session_id_key" ON "transactions"("session_id"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/0_init/migration.sql b/prisma/migrations/20260210172326_init/migration.sql similarity index 92% rename from prisma/migrations/0_init/migration.sql rename to prisma/migrations/20260210172326_init/migration.sql index 3bf510d..3e53e44 100644 --- a/prisma/migrations/0_init/migration.sql +++ b/prisma/migrations/20260210172326_init/migration.sql @@ -40,9 +40,9 @@ CREATE TABLE "market_offers" ( "buyout_price" INTEGER, "final_price" INTEGER, "status" TEXT NOT NULL, - "posted_at" TEXT DEFAULT '', - "opening_at" TEXT NOT NULL, - "closing_at" TEXT NOT NULL, + "posted_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "opening_at" DATETIME NOT NULL, + "closing_at" DATETIME NOT NULL, "buyer_id" TEXT, CONSTRAINT "market_offers_skin_uuid_fkey" FOREIGN KEY ("skin_uuid") REFERENCES "skins" ("uuid") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "market_offers_seller_id_fkey" FOREIGN KEY ("seller_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, @@ -55,7 +55,7 @@ CREATE TABLE "bids" ( "bidder_id" TEXT NOT NULL, "market_offer_id" TEXT NOT NULL, "offer_amount" INTEGER NOT NULL, - "offered_at" TEXT DEFAULT '', + "offered_at" DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "bids_bidder_id_fkey" FOREIGN KEY ("bidder_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "bids_market_offer_id_fkey" FOREIGN KEY ("market_offer_id") REFERENCES "market_offers" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); @@ -68,7 +68,7 @@ CREATE TABLE "logs" ( "target_user_id" TEXT, "coins_amount" INTEGER, "user_new_amount" INTEGER, - "created_at" TEXT DEFAULT '', + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "logs_target_user_id_fkey" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE ); @@ -85,7 +85,7 @@ CREATE TABLE "games" ( "p1_new_elo" INTEGER, "p2_new_elo" INTEGER, "type" TEXT, - "timestamp" TEXT, + "timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "games_p1_fkey" FOREIGN KEY ("p1") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "games_p2_fkey" FOREIGN KEY ("p2") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE ); @@ -129,10 +129,9 @@ CREATE TABLE "transactions" ( "customer_email" TEXT, "customer_name" TEXT, "payment_status" TEXT NOT NULL, - "created_at" TEXT DEFAULT '', + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "transactions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); -- CreateIndex CREATE UNIQUE INDEX "transactions_session_id_key" ON "transactions"("session_id"); - diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fac087b..da65b26 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -64,9 +64,9 @@ model MarketOffer { buyoutPrice Int? @map("buyout_price") finalPrice Int? @map("final_price") status String - postedAt String? @default(dbgenerated("CURRENT_TIMESTAMP")) @map("posted_at") - openingAt String @map("opening_at") - closingAt String @map("closing_at") + postedAt DateTime? @default(now()) @map("posted_at") + openingAt DateTime @map("opening_at") + closingAt DateTime @map("closing_at") buyerId String? @map("buyer_id") skin Skin @relation(fields: [skinUuid], references: [uuid]) @@ -82,7 +82,7 @@ model Bid { bidderId String @map("bidder_id") marketOfferId String @map("market_offer_id") offerAmount Int @map("offer_amount") - offeredAt String? @default(dbgenerated("CURRENT_TIMESTAMP")) @map("offered_at") + offeredAt DateTime? @default(now()) @map("offered_at") bidder User @relation(fields: [bidderId], references: [id]) marketOffer MarketOffer @relation(fields: [marketOfferId], references: [id]) @@ -97,7 +97,7 @@ model Log { targetUserId String? @map("target_user_id") coinsAmount Int? @map("coins_amount") userNewAmount Int? @map("user_new_amount") - createdAt String? @default(dbgenerated("CURRENT_TIMESTAMP")) @map("created_at") + createdAt DateTime? @default(now()) @map("created_at") user User @relation("UserLogs", fields: [userId], references: [id]) targetUser User? @relation("TargetUserLogs", fields: [targetUserId], references: [id]) @@ -116,7 +116,7 @@ model Game { p1NewElo Int? @map("p1_new_elo") p2NewElo Int? @map("p2_new_elo") type String? - timestamp String? + timestamp DateTime? @default(now()) @map("timestamp") player1 User @relation("Player1", fields: [p1], references: [id]) player2 User? @relation("Player2", fields: [p2], references: [id]) @@ -167,7 +167,7 @@ model Transaction { customerEmail String? @map("customer_email") customerName String? @map("customer_name") paymentStatus String @map("payment_status") - createdAt String? @default(dbgenerated("CURRENT_TIMESTAMP")) @map("created_at") + createdAt DateTime? @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id]) From db7d9aec8f189224df9bb1a4ac613c20370dcf03 Mon Sep 17 00:00:00 2001 From: milo Date: Wed, 11 Feb 2026 18:01:45 +0100 Subject: [PATCH 4/5] feat: new backend handled login --- package-lock.json | 72 +++++++++++++++++++- package.json | 1 + src/server/app.js | 6 +- src/server/middleware/auth.js | 63 ++++++++++++++++++ src/server/routes/api.js | 80 +++++++++++++---------- src/server/routes/auth.js | 116 +++++++++++++++++++++++++++++++++ src/server/routes/blackjack.js | 21 +++--- src/server/routes/market.js | 11 ++-- src/server/routes/monke.js | 18 ++--- src/server/routes/poker.js | 38 ++++++----- src/server/routes/solitaire.js | 28 ++++---- src/server/socket.js | 53 +++++++++------ 12 files changed, 397 insertions(+), 110 deletions(-) create mode 100644 src/server/middleware/auth.js create mode 100644 src/server/routes/auth.js diff --git a/package-lock.json b/package-lock.json index 26d4997..d035ddb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "discord.js": "^14.25.1", "dotenv": "^16.0.3", "express": "^4.18.2", + "jsonwebtoken": "^9.0.3", "node-cron": "^3.0.3", "openai": "^4.104.0", "pnpm": "^10.29.2", @@ -2729,6 +2730,34 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -2796,6 +2825,42 @@ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2803,6 +2868,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -3601,7 +3672,6 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index a009a26..e539f23 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "discord.js": "^14.25.1", "dotenv": "^16.0.3", "express": "^4.18.2", + "jsonwebtoken": "^9.0.3", "node-cron": "^3.0.3", "openai": "^4.104.0", "pnpm": "^10.29.2", diff --git a/src/server/app.js b/src/server/app.js index ff182c6..9b69e21 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -12,6 +12,7 @@ import { getSocketIo } from "./socket.js"; import { blackjackRoutes } from "./routes/blackjack.js"; import { marketRoutes } from "./routes/market.js"; import { monkeRoutes } from "./routes/monke.js"; +import { authRoutes } from "./routes/auth.js"; // --- EXPRESS APP INITIALIZATION --- const app = express(); @@ -25,7 +26,7 @@ app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", FLAPI_URL); res.header( "Access-Control-Allow-Headers", - "Content-Type, X-API-Key, ngrok-skip-browser-warning, Cache-Control, Pragma, Expires", + "Content-Type, Authorization, X-API-Key, ngrok-skip-browser-warning, Cache-Control, Pragma, Expires", ); next(); }); @@ -48,6 +49,9 @@ app.use("/public", express.static("public")); // --- API ROUTES --- +// Auth routes (Discord OAuth2, no client/io needed) +app.use("/api/auth", authRoutes()); + // General API routes (users, polls, etc.) app.use("/api", apiRoutes(client, io)); diff --git a/src/server/middleware/auth.js b/src/server/middleware/auth.js new file mode 100644 index 0000000..717bf1d --- /dev/null +++ b/src/server/middleware/auth.js @@ -0,0 +1,63 @@ +import jwt from "jsonwebtoken"; + +const JWT_SECRET = process.env.JWT_SECRET; + +/** + * Middleware that requires a valid JWT token in the Authorization header. + * Sets req.userId to the authenticated Discord user ID. + */ +export function requireAuth(req, res, next) { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return res.status(401).json({ error: "Authentication required." }); + } + + const token = authHeader.split("Bearer ")[1]; + try { + const payload = jwt.verify(token, JWT_SECRET); + req.userId = payload.discordId; + next(); + } catch (err) { + return res.status(401).json({ error: "Invalid or expired token." }); + } +} + +/** + * Optional auth middleware - attaches userId if token is present, but doesn't block. + * Useful for routes that work for both authenticated and unauthenticated users. + */ +export function optionalAuth(req, res, next) { + const authHeader = req.headers.authorization; + if (authHeader && authHeader.startsWith("Bearer ")) { + const token = authHeader.split("Bearer ")[1]; + try { + const payload = jwt.verify(token, JWT_SECRET); + req.userId = payload.discordId; + } catch { + // Token invalid, continue without userId + } + } + next(); +} + +/** + * Signs a JWT token for a given Discord user ID. + * @param {string} discordId - The Discord user ID. + * @returns {string} The signed JWT token. + */ +export function signToken(discordId) { + return jwt.sign({ discordId }, JWT_SECRET, { expiresIn: "7d" }); +} + +/** + * Verifies a JWT token and returns the payload. + * @param {string} token - The JWT token to verify. + * @returns {object|null} The decoded payload or null if invalid. + */ +export function verifyToken(token) { + try { + return jwt.verify(token, JWT_SECRET); + } catch { + return null; + } +} diff --git a/src/server/routes/api.js b/src/server/routes/api.js index 778a170..1dc5ad5 100644 --- a/src/server/routes/api.js +++ b/src/server/routes/api.js @@ -22,6 +22,7 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "disc import { emitDataUpdated, socketEmit, onGameOver } from "../socket.js"; import { handleCaseOpening } from "../../utils/marketNotifs.js"; import { drawCaseContent, drawCaseSkin, getSkinUpgradeProbs } from "../../utils/caseOpening.js"; +import { requireAuth } from "../middleware/auth.js"; // Create a new router instance const router = express.Router(); @@ -59,8 +60,8 @@ export function apiRoutes(client, io) { } }); - router.post("/register-user", async (req, res) => { - const { discordUserId } = req.body; + router.post("/register-user", requireAuth, async (req, res) => { + const discordUserId = req.userId; const discordUser = await client.users.fetch(discordUserId); try { @@ -104,8 +105,9 @@ export function apiRoutes(client, io) { } }); - router.post("/open-case", async (req, res) => { - const { userId, caseType } = req.body; + router.post("/open-case", requireAuth, async (req, res) => { + const userId = req.userId; + const { caseType } = req.body; let caseTypeVal; switch (caseType) { @@ -231,8 +233,8 @@ export function apiRoutes(client, io) { } }); - router.post("/skin/:uuid/instant-sell", async (req, res) => { - const { userId } = req.body; + router.post("/skin/:uuid/instant-sell", requireAuth, async (req, res) => { + const userId = req.userId; try { const skin = await skinService.getSkin(req.params.uuid); const skinData = skins.find((s) => s.uuid === skin.uuid); @@ -300,8 +302,8 @@ export function apiRoutes(client, io) { } }); - router.post("/skin-upgrade/:uuid", async (req, res) => { - const { userId } = req.body; + router.post("/skin-upgrade/:uuid", requireAuth, async (req, res) => { + const userId = req.userId; try { const skin = await skinService.getSkin(req.params.uuid); const skinData = skins.find((s) => s.uuid === skin.uuid); @@ -518,8 +520,8 @@ export function apiRoutes(client, io) { } }); - router.get("/user/:id/daily", async (req, res) => { - const { id } = req.params; + router.get("/user/:id/daily", requireAuth, async (req, res) => { + const id = req.userId; try { const akhy = await userService.getUser(id); if (!akhy) return res.status(404).json({ message: "Utilisateur introuvable" }); @@ -551,9 +553,9 @@ export function apiRoutes(client, io) { res.json({ activePolls }); }); - router.post("/timedout", async (req, res) => { + router.post("/timedout", requireAuth, async (req, res) => { try { - const { userId } = req.body; + const userId = req.userId; const guild = await client.guilds.fetch(process.env.GUILD_ID); const member = await guild.members.fetch(userId); res.status(200).json({ isTimedOut: member?.isCommunicationDisabled() || false }); @@ -564,8 +566,9 @@ export function apiRoutes(client, io) { // --- Shop & Interaction Routes --- - router.post("/change-nickname", async (req, res) => { - const { userId, nickname, commandUserId } = req.body; + router.post("/change-nickname", requireAuth, async (req, res) => { + const { userId, nickname } = req.body; + const commandUserId = req.userId; const commandUser = await userService.getUser(commandUserId); if (!commandUser) return res.status(404).json({ message: "Command user not found." }); if (commandUser.coins < 1000) return res.status(403).json({ message: "Pas assez de FlopoCoins (1000 requis)." }); @@ -614,8 +617,9 @@ export function apiRoutes(client, io) { } }); - router.post("/spam-ping", async (req, res) => { - const { userId, commandUserId } = req.body; + router.post("/spam-ping", requireAuth, async (req, res) => { + const { userId } = req.body; + const commandUserId = req.userId; const user = await userService.getUser(userId); const commandUser = await userService.getUser(commandUserId); @@ -671,8 +675,9 @@ export function apiRoutes(client, io) { res.status(200).json({ slowmodes: activeSlowmodes }); }); - router.post("/slowmode", async (req, res) => { - let { userId, commandUserId } = req.body; + router.post("/slowmode", requireAuth, async (req, res) => { + let { userId } = req.body; + const commandUserId = req.userId; const user = await userService.getUser(userId); const commandUser = await userService.getUser(commandUserId); @@ -761,8 +766,9 @@ export function apiRoutes(client, io) { // --- Time-Out Route --- - router.post("/timeout", async (req, res) => { - let { userId, commandUserId } = req.body; + router.post("/timeout", requireAuth, async (req, res) => { + let { userId } = req.body; + const commandUserId = req.userId; const user = await userService.getUser(userId); const commandUser = await userService.getUser(commandUserId); @@ -877,8 +883,9 @@ export function apiRoutes(client, io) { res.status(200).json({ predis: reversedPredis }); }); - router.post("/start-predi", async (req, res) => { - let { commandUserId, label, options, closingTime, payoutTime } = req.body; + router.post("/start-predi", requireAuth, async (req, res) => { + let { label, options, closingTime, payoutTime } = req.body; + const commandUserId = req.userId; const commandUser = await userService.getUser(commandUserId); @@ -973,8 +980,9 @@ export function apiRoutes(client, io) { return res.status(200).json({ message: `Ta prédi '${label}' a commencée !` }); }); - router.post("/vote-predi", async (req, res) => { - const { commandUserId, predi, amount, option } = req.body; + router.post("/vote-predi", requireAuth, async (req, res) => { + const { predi, amount, option } = req.body; + const commandUserId = req.userId; let warning = false; @@ -1041,8 +1049,9 @@ export function apiRoutes(client, io) { return res.status(200).send({ message: `Vote enregistré!` }); }); - router.post("/end-predi", async (req, res) => { - const { commandUserId, predi, confirm, winningOption } = req.body; + router.post("/end-predi", requireAuth, async (req, res) => { + const { predi, confirm, winningOption } = req.body; + const commandUserId = req.userId; const commandUser = await userService.getUser(commandUserId); if (!commandUser) return res.status(403).send({ message: "Oups, je ne te connais pas" }); @@ -1156,8 +1165,9 @@ export function apiRoutes(client, io) { return res.status(200).json({ message: "Prédi close" }); }); - router.post("/snake/reward", async (req, res) => { - const { discordId, score, isWin } = req.body; + router.post("/snake/reward", requireAuth, async (req, res) => { + const discordId = req.userId; + const { score, isWin } = req.body; console.log(`[SNAKE][SOLO]${discordId}: score=${score}, isWin=${isWin}`); try { const user = await userService.getUser(discordId); @@ -1181,8 +1191,9 @@ export function apiRoutes(client, io) { } }); - router.post("/queue/leave", async (req, res) => { - const { discordId, game, reason } = req.body; + router.post("/queue/leave", requireAuth, async (req, res) => { + const discordId = req.userId; + const { game, reason } = req.body; if (game === "snake" && (reason === "beforeunload" || reason === "route-leave")) { const lobby = Object.values(activeSnakeGames).find( (l) => (l.p1.id === discordId || l.p2.id === discordId) && !l.gameOver, @@ -1240,11 +1251,12 @@ export function apiRoutes(client, io) { res.json({ offers: COIN_OFFERS }); }); - router.post("/create-checkout-session", async (req, res) => { - const { userId, offerId } = req.body; + router.post("/create-checkout-session", requireAuth, async (req, res) => { + const userId = req.userId; + const { offerId } = req.body; - if (!userId || !offerId) { - return res.status(400).json({ error: "Missing required fields: userId, offerId" }); + if (!offerId) { + return res.status(400).json({ error: "Missing required field: offerId" }); } const offer = COIN_OFFERS.find((o) => o.id === offerId); diff --git a/src/server/routes/auth.js b/src/server/routes/auth.js new file mode 100644 index 0000000..0d0397c --- /dev/null +++ b/src/server/routes/auth.js @@ -0,0 +1,116 @@ +import express from "express"; +import axios from "axios"; +import { signToken } from "../middleware/auth.js"; +import * as userService from "../../services/user.service.js"; + +const router = express.Router(); + +const DISCORD_API = "https://discord.com/api/v10"; +const DISCORD_CLIENT_ID = process.env.APP_ID; +const DISCORD_CLIENT_SECRET = process.env.DISCORD_CLIENT_SECRET; +const API_URL = process.env.DEV_SITE === "true" ? process.env.API_URL_DEV : process.env.API_URL; +const FLAPI_URL = process.env.DEV_SITE === "true" ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL; +const REDIRECT_URI = `${API_URL}/api/auth/discord/callback`; + +/** + * GET /api/auth/discord + * Redirects the user to Discord's OAuth2 authorization page. + */ +router.get("/discord", (req, res) => { + const params = new URLSearchParams({ + client_id: DISCORD_CLIENT_ID, + redirect_uri: REDIRECT_URI, + response_type: "code", + scope: "identify", + }); + console.log("Redirecting to Discord OAuth2 with params:", params.toString()); + res.redirect(`${DISCORD_API}/oauth2/authorize?${params.toString()}`); +}); + +/** + * GET /api/auth/discord/callback + * Handles the OAuth2 callback from Discord. + * Exchanges the authorization code for tokens, fetches user info, + * creates a JWT, and redirects the user back to the frontend. + */ +router.get("/discord/callback", async (req, res) => { + const { code } = req.query; + if (!code) { + return res.status(400).json({ error: "Missing authorization code." }); + } + + try { + // Exchange the authorization code for an access token + const tokenResponse = await axios.post( + `${DISCORD_API}/oauth2/token`, + new URLSearchParams({ + client_id: DISCORD_CLIENT_ID, + client_secret: DISCORD_CLIENT_SECRET, + grant_type: "authorization_code", + code, + redirect_uri: REDIRECT_URI, + }), + { headers: { "Content-Type": "application/x-www-form-urlencoded" } }, + ); + + const { access_token } = tokenResponse.data; + + // Fetch the user's Discord profile + const userResponse = await axios.get(`${DISCORD_API}/users/@me`, { + headers: { Authorization: `Bearer ${access_token}` }, + }); + + const discordUser = userResponse.data; + + // Ensure the user exists in our database + const existingUser = await userService.getUser(discordUser.id); + if (existingUser) { + // Update avatar if it changed + const avatarUrl = discordUser.avatar + ? `https://cdn.discordapp.com/avatars/${discordUser.id}/${discordUser.avatar}.png?size=256` + : null; + if (avatarUrl) { + await userService.updateUserAvatar(discordUser.id, avatarUrl); + } + } + + // Sign a JWT with the verified Discord ID + const token = signToken(discordUser.id); + + // Redirect back to the frontend with the token + res.redirect(`${FLAPI_URL}/auth/callback?token=${token}&discordId=${discordUser.id}`); + } catch (error) { + console.error("Discord OAuth2 error:", error.response?.data || error.message); + res.redirect(`${FLAPI_URL}/auth/callback?error=auth_failed`); + } +}); + +/** + * GET /api/auth/me + * Returns the authenticated user's info. Requires a valid JWT. + */ +router.get("/me", async (req, res) => { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return res.status(401).json({ error: "Authentication required." }); + } + + const token = authHeader.split("Bearer ")[1]; + const { verifyToken } = await import("../middleware/auth.js"); + const payload = verifyToken(token); + if (!payload) { + return res.status(401).json({ error: "Invalid or expired token." }); + } + + const user = await userService.getUser(payload.discordId); + if (!user) { + console.warn("User not found for Discord ID in token:", payload.discordId); + return res.json({discordId: payload.discordId}); + } + + res.json({ user, discordId: user.id }); +}); + +export function authRoutes() { + return router; +} diff --git a/src/server/routes/blackjack.js b/src/server/routes/blackjack.js index 04859cd..3d2716a 100644 --- a/src/server/routes/blackjack.js +++ b/src/server/routes/blackjack.js @@ -20,6 +20,7 @@ import * as logService from "../../services/log.service.js"; import { client } from "../../bot/client.js"; import { emitToast, emitUpdate, emitPlayerUpdate } from "../socket.js"; import { EmbedBuilder, time } from "discord.js"; +import { requireAuth } from "../middleware/auth.js"; export function blackjackRoutes(io) { const router = express.Router(); @@ -123,9 +124,8 @@ export function blackjackRoutes(io) { // --- Public endpoints --- router.get("/", (req, res) => res.status(200).json({ room: snapshot(room) })); - router.post("/join", async (req, res) => { - const { userId } = req.body; - if (!userId) return res.status(400).json({ message: "userId required" }); + router.post("/join", requireAuth, async (req, res) => { + const userId = req.userId; if (room.players[userId]) return res.status(200).json({ message: "Already here" }); const user = await client.users.fetch(userId); @@ -191,9 +191,9 @@ export function blackjackRoutes(io) { return res.status(200).json({ message: "joined" }); }); - router.post("/leave", async (req, res) => { - const { userId } = req.body; - if (!userId || !room.players[userId]) return res.status(403).json({ message: "not in room" }); + router.post("/leave", requireAuth, async (req, res) => { + const userId = req.userId; + if (!room.players[userId]) return res.status(403).json({ message: "not in room" }); try { const guild = await client.guilds.fetch(process.env.GUILD_ID); @@ -238,8 +238,9 @@ export function blackjackRoutes(io) { } }); - router.post("/bet", async (req, res) => { - const { userId, amount } = req.body; + router.post("/bet", requireAuth, async (req, res) => { + const userId = req.userId; + const { amount } = req.body; const p = room.players[userId]; if (!p) return res.status(404).json({ message: "not in room" }); if (room.status !== "betting") return res.status(403).json({ message: "betting-closed" }); @@ -270,8 +271,8 @@ export function blackjackRoutes(io) { return res.status(200).json({ message: "bet-accepted" }); }); - router.post("/action/:action", async (req, res) => { - const { userId } = req.body; + router.post("/action/:action", requireAuth, async (req, res) => { + const userId = req.userId; const action = req.params.action; const p = room.players[userId]; if (!p) return res.status(404).json({ message: "not in room" }); diff --git a/src/server/routes/market.js b/src/server/routes/market.js index 6303630..60f7912 100644 --- a/src/server/routes/market.js +++ b/src/server/routes/market.js @@ -11,6 +11,7 @@ import * as logService from "../../services/log.service.js"; import * as marketService from "../../services/market.service.js"; import { emitMarketUpdate } from "../socket.js"; import { handleNewMarketOffer, handleNewMarketOfferBid } from "../../utils/marketNotifs.js"; +import { requireAuth } from "../middleware/auth.js"; // Create a new router instance const router = express.Router(); @@ -63,8 +64,9 @@ export function marketRoutes(client, io) { } }); - router.post("/place-offer", async (req, res) => { - const { seller_id, skin_uuid, starting_price, delay, duration, timestamp } = req.body; + router.post("/place-offer", requireAuth, async (req, res) => { + const seller_id = req.userId; + const { skin_uuid, starting_price, delay, duration, timestamp } = req.body; const now = Date.now(); try { const skin = await skinService.getSkin(skin_uuid); @@ -104,8 +106,9 @@ export function marketRoutes(client, io) { } }); - router.post("/offers/:id/place-bid", async (req, res) => { - const { buyer_id, bid_amount, timestamp } = req.body; + router.post("/offers/:id/place-bid", requireAuth, async (req, res) => { + const buyer_id = req.userId; + const { bid_amount, timestamp } = req.body; try { const offer = await marketService.getMarketOfferById(req.params.id); if (!offer) return res.status(404).send({ error: "Offer not found" }); diff --git a/src/server/routes/monke.js b/src/server/routes/monke.js index 424f3a7..6ef00f6 100644 --- a/src/server/routes/monke.js +++ b/src/server/routes/monke.js @@ -5,6 +5,7 @@ import { socketEmit } from "../socket.js"; import * as userService from "../../services/user.service.js"; import * as logService from "../../services/log.service.js"; import { init } from "openai/_shims/index.mjs"; +import { requireAuth } from "../middleware/auth.js"; const router = express.Router(); @@ -29,11 +30,9 @@ export function monkeRoutes(client, io) { return res.status(200).json({ userGamePath }); }); - router.post("/:userId/start", async (req, res) => { - const { userId } = req.params; + router.post("/:userId/start", requireAuth, async (req, res) => { + const userId = req.userId; const { initialBet } = req.body; - - if (!userId) return res.status(400).json({ error: "User ID is required" }); const user = await userService.getUser(userId); if (!user) return res.status(404).json({ error: "User not found" }); if (!initialBet) return res.status(400).json({ error: "Initial bet is required" }); @@ -61,11 +60,9 @@ export function monkeRoutes(client, io) { return res.status(200).json({ message: "Monke game started", userGamePath: monkePaths[userId] }); }); - router.post("/:userId/play", async (req, res) => { - const { userId } = req.params; + router.post("/:userId/play", requireAuth, async (req, res) => { + const userId = req.userId; const { choice, step } = req.body; - - if (!userId) return res.status(400).json({ error: "User ID is required" }); const user = await userService.getUser(userId); if (!user) return res.status(404).json({ error: "User not found" }); if (!monkePaths[userId]) return res.status(400).json({ error: "No active game found for this user" }); @@ -105,9 +102,8 @@ export function monkeRoutes(client, io) { } }); - router.post("/:userId/stop", async (req, res) => { - const { userId } = req.params; - if (!userId) return res.status(400).json({ error: "User ID is required" }); + router.post("/:userId/stop", requireAuth, async (req, res) => { + const userId = req.userId; const user = await userService.getUser(userId); if (!user) return res.status(404).json({ error: "User not found" }); if (!monkePaths[userId]) return res.status(400).json({ error: "No active game found for this user" }); diff --git a/src/server/routes/poker.js b/src/server/routes/poker.js index fd217ad..f36db1e 100644 --- a/src/server/routes/poker.js +++ b/src/server/routes/poker.js @@ -17,6 +17,7 @@ import { client } from "../../bot/client.js"; import { emitPokerToast, emitPokerUpdate } from "../socket.js"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; import { formatAmount } from "../../utils/index.js"; +import { requireAuth } from "../middleware/auth.js"; const { Hand } = pkg; @@ -44,9 +45,9 @@ export function pokerRoutes(client, io) { } }); - router.post("/create", async (req, res) => { - const { creatorId, minBet, fakeMoney } = req.body; - if (!creatorId) return res.status(400).json({ message: "Creator ID is required." }); + router.post("/create", requireAuth, async (req, res) => { + const creatorId = req.userId; + const { minBet, fakeMoney } = req.body; if (Object.values(pokerRooms).some((room) => room.host_id === creatorId || room.players[creatorId])) { return res.status(403).json({ message: "You are already in a poker room." }); @@ -125,9 +126,10 @@ export function pokerRoutes(client, io) { res.status(201).json({ roomId: id }); }); - router.post("/join", async (req, res) => { - const { userId, roomId } = req.body; - if (!userId || !roomId) return res.status(400).json({ message: "User ID and Room ID are required." }); + router.post("/join", requireAuth, async (req, res) => { + const userId = req.userId; + const { roomId } = req.body; + if (!roomId) return res.status(400).json({ message: "Room ID is required." }); if (!pokerRooms[roomId]) return res.status(404).json({ message: "Room not found." }); if (Object.values(pokerRooms).some((r) => r.players[userId] || r.queue[userId])) { return res.status(403).json({ message: "You are already in a room or queue." }); @@ -143,8 +145,9 @@ export function pokerRoutes(client, io) { res.status(200).json({ message: "Successfully joined." }); }); - router.post("/accept", async (req, res) => { - const { hostId, playerId, roomId } = req.body; + router.post("/accept", requireAuth, async (req, res) => { + const hostId = req.userId; + const { playerId, roomId } = req.body; const room = pokerRooms[roomId]; if (!room || room.host_id !== hostId || !room.queue[playerId]) { return res.status(403).json({ message: "Unauthorized or player not in queue." }); @@ -172,8 +175,9 @@ export function pokerRoutes(client, io) { res.status(200).json({ message: "Player accepted." }); }); - router.post("/leave", async (req, res) => { - const { userId, roomId } = req.body; + router.post("/leave", requireAuth, async (req, res) => { + const userId = req.userId; + const { roomId } = req.body; if (!pokerRooms[roomId]) return res.status(404).send({ message: "Table introuvable" }); if (!pokerRooms[roomId].players[userId]) return res.status(404).send({ message: "Joueur introuvable" }); @@ -223,8 +227,9 @@ export function pokerRoutes(client, io) { return res.status(200); }); - router.post("/kick", async (req, res) => { - const { commandUserId, userId, roomId } = req.body; + router.post("/kick", requireAuth, async (req, res) => { + const commandUserId = req.userId; + const { userId, roomId } = req.body; if (!pokerRooms[roomId]) return res.status(404).send({ message: "Table introuvable" }); if (!pokerRooms[roomId].players[commandUserId]) return res.status(404).send({ message: "Joueur introuvable" }); @@ -265,7 +270,7 @@ export function pokerRoutes(client, io) { // --- Game Action Endpoints --- - router.post("/start", async (req, res) => { + router.post("/start", requireAuth, async (req, res) => { const { roomId } = req.body; const room = pokerRooms[roomId]; if (!room) return res.status(404).json({ message: "Room not found." }); @@ -276,7 +281,7 @@ export function pokerRoutes(client, io) { }); // NEW: Endpoint to start the next hand - router.post("/next-hand", async (req, res) => { + router.post("/next-hand", requireAuth, async (req, res) => { const { roomId } = req.body; const room = pokerRooms[roomId]; if (!room || !room.waiting_for_restart) { @@ -286,8 +291,9 @@ export function pokerRoutes(client, io) { res.status(200).json({ message: "Next hand started." }); }); - router.post("/action/:action", async (req, res) => { - const { playerId, amount, roomId } = req.body; + router.post("/action/:action", requireAuth, async (req, res) => { + const playerId = req.userId; + const { amount, roomId } = req.body; const { action } = req.params; const room = pokerRooms[roomId]; diff --git a/src/server/routes/solitaire.js b/src/server/routes/solitaire.js index 97a8a55..e9d87fa 100644 --- a/src/server/routes/solitaire.js +++ b/src/server/routes/solitaire.js @@ -23,6 +23,7 @@ import * as userService from "../../services/user.service.js"; import * as logService from "../../services/log.service.js"; import * as solitaireService from "../../services/solitaire.service.js"; import { socketEmit } from "../socket.js"; +import { requireAuth } from "../middleware/auth.js"; // Create a new router instance const router = express.Router(); @@ -36,9 +37,9 @@ const router = express.Router(); export function solitaireRoutes(client, io) { // --- Game Initialization Endpoints --- - router.post("/start", (req, res) => { - const { userId, userSeed, hardMode } = req.body; - if (!userId) return res.status(400).json({ error: "User ID is required." }); + router.post("/start", requireAuth, (req, res) => { + const userId = req.userId; + const { userSeed, hardMode } = req.body; // If a game already exists for the user, return it instead of creating a new one. if (activeSolitaireGames[userId] && !activeSolitaireGames[userId].isSOTD) { @@ -78,8 +79,8 @@ export function solitaireRoutes(client, io) { res.json({ success: true, gameState }); }); - router.post("/start/sotd", async (req, res) => { - const { userId } = req.body; + router.post("/start/sotd", requireAuth, async (req, res) => { + const userId = req.userId; /*if (!userId || !getUser.get(userId)) { return res.status(404).json({ error: 'User not found.' }); }*/ @@ -138,16 +139,17 @@ export function solitaireRoutes(client, io) { } }); - router.post("/reset", (req, res) => { - const { userId } = req.body; + router.post("/reset", requireAuth, (req, res) => { + const userId = req.userId; if (activeSolitaireGames[userId]) { delete activeSolitaireGames[userId]; } res.json({ success: true, message: "Game reset." }); }); - router.post("/move", async (req, res) => { - const { userId, ...moveData } = req.body; + router.post("/move", requireAuth, async (req, res) => { + const userId = req.userId; + const { ...moveData } = req.body; const gameState = activeSolitaireGames[userId]; if (!gameState) return res.status(404).json({ error: "Game not found." }); @@ -176,8 +178,8 @@ export function solitaireRoutes(client, io) { } }); - router.post("/draw", (req, res) => { - const { userId } = req.body; + router.post("/draw", requireAuth, (req, res) => { + const userId = req.userId; const gameState = activeSolitaireGames[userId]; if (!gameState) return res.status(404).json({ error: "Game not found." }); @@ -192,8 +194,8 @@ export function solitaireRoutes(client, io) { res.json({ success: true, gameState }); }); - router.post("/undo", (req, res) => { - const { userId } = req.body; + router.post("/undo", requireAuth, (req, res) => { + const userId = req.userId; const gameState = activeSolitaireGames[userId]; if (!gameState) return res.status(404).json({ error: "Game not found." }); diff --git a/src/server/socket.js b/src/server/socket.js index f07a1fc..78c3f92 100644 --- a/src/server/socket.js +++ b/src/server/socket.js @@ -16,6 +16,7 @@ import { formatConnect4BoardForDiscord, } from "../game/various.js"; import { eloHandler } from "../game/elo.js"; +import { verifyToken } from "./middleware/auth.js"; // --- Module-level State --- let io; @@ -25,10 +26,23 @@ let io; export function initializeSocket(server, client) { io = server; + // Authenticate socket connections via JWT (optional - allows unauthenticated connections for health checks) + io.use((socket, next) => { + const token = socket.handshake.auth?.token; + if (token) { + const payload = verifyToken(token); + if (!payload) { + return next(new Error("Invalid or expired token")); + } + socket.userId = payload.discordId; + } + next(); + }); + io.on("connection", (socket) => { - socket.on("user-connected", async (userId) => { - if (!userId) return; - await refreshQueuesForUser(userId, client); + socket.on("user-connected", async () => { + if (!socket.userId) return; + await refreshQueuesForUser(socket.userId, client); }); registerTicTacToeEvents(socket, client); @@ -39,14 +53,13 @@ export function initializeSocket(server, client) { io.emit("blackjack:chat", data); }); - socket.on("tictactoe:queue:leave", async ({ discordId }) => await refreshQueuesForUser(discordId, client)); - socket.on("connect4:queue:leave", async ({ discordId }) => await refreshQueuesForUser(discordId, client)); - socket.on("snake:queue:leave", async ({ discordId }) => await refreshQueuesForUser(discordId, client)); + socket.on("tictactoe:queue:leave", async () => await refreshQueuesForUser(socket.userId, client)); + socket.on("connect4:queue:leave", async () => await refreshQueuesForUser(socket.userId, client)); + socket.on("snake:queue:leave", async () => await refreshQueuesForUser(socket.userId, client)); // catch tab kills / network drops socket.on("disconnecting", async () => { - const discordId = socket.handshake.auth?.discordId; // or your mapping - await refreshQueuesForUser(discordId, client); + await refreshQueuesForUser(socket.userId, client); }); socket.on("disconnect", () => { @@ -64,24 +77,24 @@ export function getSocketIo() { // --- Event Registration --- function registerTicTacToeEvents(socket, client) { - socket.on("tictactoeconnection", (e) => refreshQueuesForUser(e.id, client)); - socket.on("tictactoequeue", (e) => onQueueJoin(client, "tictactoe", e.playerId)); - socket.on("tictactoeplaying", (e) => onTicTacToeMove(client, e)); - socket.on("tictactoegameOver", (e) => onGameOver(client, "tictactoe", e.playerId, e.winner)); + socket.on("tictactoeconnection", () => refreshQueuesForUser(socket.userId, client)); + socket.on("tictactoequeue", () => onQueueJoin(client, "tictactoe", socket.userId)); + socket.on("tictactoeplaying", (e) => onTicTacToeMove(client, { ...e, playerId: socket.userId })); + socket.on("tictactoegameOver", (e) => onGameOver(client, "tictactoe", socket.userId, e.winner)); } function registerConnect4Events(socket, client) { - socket.on("connect4connection", (e) => refreshQueuesForUser(e.id, client)); - socket.on("connect4queue", (e) => onQueueJoin(client, "connect4", e.playerId)); - socket.on("connect4playing", (e) => onConnect4Move(client, e)); - socket.on("connect4NoTime", (e) => onGameOver(client, "connect4", e.playerId, e.winner, "(temps écoulé)")); + socket.on("connect4connection", () => refreshQueuesForUser(socket.userId, client)); + socket.on("connect4queue", () => onQueueJoin(client, "connect4", socket.userId)); + socket.on("connect4playing", (e) => onConnect4Move(client, { ...e, playerId: socket.userId })); + socket.on("connect4NoTime", (e) => onGameOver(client, "connect4", socket.userId, e.winner, "(temps écoulé)")); } function registerSnakeEvents(socket, client) { - socket.on("snakeconnection", (e) => refreshQueuesForUser(e.id, client)); - socket.on("snakequeue", (e) => onQueueJoin(client, "snake", e.playerId)); - socket.on("snakegamestate", (e) => onSnakeGameStateUpdate(client, e)); - socket.on("snakegameOver", (e) => onGameOver(client, "snake", e.playerId, e.winner)); + socket.on("snakeconnection", () => refreshQueuesForUser(socket.userId, client)); + socket.on("snakequeue", () => onQueueJoin(client, "snake", socket.userId)); + socket.on("snakegamestate", (e) => onSnakeGameStateUpdate(client, { ...e, playerId: socket.userId })); + socket.on("snakegameOver", (e) => onGameOver(client, "snake", socket.userId, e.winner)); } // --- Core Handlers (Preserving Original Logic) --- From 3115df3cdd5952a874257ac18f4c3e00e87d4a6d Mon Sep 17 00:00:00 2001 From: milo Date: Sun, 15 Feb 2026 11:52:39 +0100 Subject: [PATCH 5/5] blackjack split fix --- src/game/blackjack.js | 5 ++++- src/server/routes/blackjack.js | 18 ++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/game/blackjack.js b/src/game/blackjack.js index 81c8083..18177b1 100644 --- a/src/game/blackjack.js +++ b/src/game/blackjack.js @@ -361,7 +361,10 @@ export function applyAction(room, playerId, action) { if (hand.stood || hand.busted) throw new Error("Already ended"); hand.hasActed = true; hand.cards.push(draw(room.shoe)); - if (isBust(hand.cards)) hand.busted = true; + if (isBust(hand.cards)) { + hand.busted = true; + p.activeHand++; + } return "hit"; } case "stand": { diff --git a/src/server/routes/blackjack.js b/src/server/routes/blackjack.js index 3d2716a..5d01d99 100644 --- a/src/server/routes/blackjack.js +++ b/src/server/routes/blackjack.js @@ -81,25 +81,23 @@ export function blackjackRoutes(io) { for (const p of Object.values(room.players)) { try { if (!p.inRound) continue; - const h = p.hands[p.activeHand]; - if (h && !h.hasActed && !h.busted && !h.stood && !h.surrendered) { - h.surrendered = true; + // Handle all remaining hands (important after splits) + for (let i = p.activeHand; i < p.hands.length; i++) { + const h = p.hands[i]; + if (!h || h.busted || h.stood || h.surrendered) continue; h.stood = true; h.hasActed = true; - //room.leavingAfterRound[p.id] = true; // kick at end of round - emitToast({ type: "player-timeout", userId: p.id }); changed = true; - } else if (h && h.hasActed && !h.stood) { - h.stood = true; - //room.leavingAfterRound[p.id] = true; // kick at end of round + } + if (changed) { + p.activeHand = p.hands.length; emitToast({ type: "player-auto-stand", userId: p.id }); - changed = true; } } catch (e) { console.log(e); } } - if (changed) emitUpdate("auto-surrender", snapshot(room)); + //if (changed) emitUpdate("auto-surrender", snapshot(room)); return changed; }