mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-03-18 21:40:27 +01:00
17
README.md
17
README.md
@@ -7,9 +7,9 @@
|
|||||||
```
|
```
|
||||||
├── public/
|
├── public/
|
||||||
│ └── images/ # Static assets
|
│ └── images/ # Static assets
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── api/ # External API integrations
|
│ ├── api/ # External API integrations
|
||||||
│ ├── bot/
|
│ ├── bot/
|
||||||
│ │ ├── commands/ # Slash command implementations
|
│ │ ├── commands/ # Slash command implementations
|
||||||
│ │ ├── components/ # Discord message components
|
│ │ ├── components/ # Discord message components
|
||||||
│ │ ├── handlers/ # Event handlers
|
│ │ ├── handlers/ # Event handlers
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
│ │ └── events.js # Event registration
|
│ │ └── events.js # Event registration
|
||||||
│ ├── config/
|
│ ├── config/
|
||||||
│ │ └── commands.js # Slash command definitions
|
│ │ └── commands.js # Slash command definitions
|
||||||
│ ├── database/
|
│ ├── database/
|
||||||
│ │ └── index.js # Database connection and models
|
│ │ └── index.js # Database connection and models
|
||||||
│ ├── game/ # Game logic and data
|
│ ├── game/ # Game logic and data
|
||||||
│ ├── server/
|
│ ├── server/
|
||||||
│ │ ├── routes/ # Express routes
|
│ │ ├── routes/ # Express routes
|
||||||
│ │ ├── app.js # Express app setup
|
│ │ ├── app.js # Express app setup
|
||||||
│ │ └── socket.js # Socket.io setup
|
│ │ └── socket.js # Socket.io setup
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Moderation Tools** : Includes commands for managing server members.
|
- **Moderation Tools** : Includes commands for managing server members.
|
||||||
- **AI Integration** : Utilizes AI APIs for enhanced interactions.
|
- **AI Integration** : Utilizes AI APIs for enhanced interactions.
|
||||||
- **Game Mechanics** : Implements game features and logic.
|
- **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)).
|
- **Web Integration** : Designed to work alongside a [FlopoSite](https://floposite.com) (see [FlopoSite's repo)](https://github.com/cassoule/floposite)).
|
||||||
|
|
||||||
## Additional Information
|
## 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 :)
|
FlopoSite though is public and can be used by anyone :)
|
||||||
|
|
||||||
## Related Links
|
## Related Links
|
||||||
|
|
||||||
- [FlopoSite Website](https://floposite.com)
|
- [FlopoSite Website](https://floposite.com)
|
||||||
- [FlopoSite Repository](https://github.com/cassoule/floposite)
|
- [FlopoSite Repository](https://github.com/cassoule/floposite)
|
||||||
|
|||||||
128
package-lock.json
generated
128
package-lock.json
generated
@@ -14,11 +14,13 @@
|
|||||||
"@prisma/client": "^6.19.2",
|
"@prisma/client": "^6.19.2",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"discord-interactions": "^4.0.0",
|
"discord-interactions": "^4.0.0",
|
||||||
"discord.js": "^14.18.0",
|
"discord.js": "^14.25.1",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"openai": "^4.104.0",
|
"openai": "^4.104.0",
|
||||||
|
"pnpm": "^10.29.2",
|
||||||
"pokersolver": "^2.1.4",
|
"pokersolver": "^2.1.4",
|
||||||
"prisma": "^6.19.2",
|
"prisma": "^6.19.2",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
@@ -700,9 +702,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vladfrangu/async_event_emitter": {
|
"node_modules/@vladfrangu/async_event_emitter": {
|
||||||
"version": "2.4.6",
|
"version": "2.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
|
||||||
"integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==",
|
"integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v14.0.0",
|
"node": ">=v14.0.0",
|
||||||
@@ -740,6 +742,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -856,13 +859,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.13.2",
|
"version": "1.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.11",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.5",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1651,6 +1654,7 @@
|
|||||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -2070,9 +2074,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.9",
|
"version": "1.15.11",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -2106,9 +2110,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
@@ -2726,6 +2730,34 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/jwa": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
||||||
@@ -2793,6 +2825,42 @@
|
|||||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@@ -2800,6 +2868,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/lodash.snakecase": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
||||||
@@ -2813,9 +2887,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/magic-bytes.js": {
|
"node_modules/magic-bytes.js": {
|
||||||
"version": "1.12.1",
|
"version": "1.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
|
||||||
"integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==",
|
"integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
@@ -3323,6 +3397,22 @@
|
|||||||
"pathe": "^2.0.3"
|
"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": {
|
"node_modules/pokersolver": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/pokersolver/-/pokersolver-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/pokersolver/-/pokersolver-2.1.4.tgz",
|
||||||
@@ -3364,6 +3454,7 @@
|
|||||||
"integrity": "sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==",
|
"integrity": "sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/config": "6.19.2",
|
"@prisma/config": "6.19.2",
|
||||||
"@prisma/engines": "6.19.2"
|
"@prisma/engines": "6.19.2"
|
||||||
@@ -3581,7 +3672,6 @@
|
|||||||
"version": "7.6.3",
|
"version": "7.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
"register": "node commands.js",
|
"register": "node commands.js",
|
||||||
"dev": "nodemon index.js",
|
"dev": "nodemon index.js",
|
||||||
"prisma:generate": "prisma generate",
|
"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",
|
"author": "Milo Gourvest",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -23,11 +26,13 @@
|
|||||||
"@prisma/client": "^6.19.2",
|
"@prisma/client": "^6.19.2",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"discord-interactions": "^4.0.0",
|
"discord-interactions": "^4.0.0",
|
||||||
"discord.js": "^14.18.0",
|
"discord.js": "^14.25.1",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"openai": "^4.104.0",
|
"openai": "^4.104.0",
|
||||||
|
"pnpm": "^10.29.2",
|
||||||
"pokersolver": "^2.1.4",
|
"pokersolver": "^2.1.4",
|
||||||
"prisma": "^6.19.2",
|
"prisma": "^6.19.2",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
|
|||||||
3167
pnpm-lock.yaml
generated
Normal file
3167
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -40,9 +40,9 @@ CREATE TABLE "market_offers" (
|
|||||||
"buyout_price" INTEGER,
|
"buyout_price" INTEGER,
|
||||||
"final_price" INTEGER,
|
"final_price" INTEGER,
|
||||||
"status" TEXT NOT NULL,
|
"status" TEXT NOT NULL,
|
||||||
"posted_at" TEXT DEFAULT '',
|
"posted_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
"opening_at" TEXT NOT NULL,
|
"opening_at" DATETIME NOT NULL,
|
||||||
"closing_at" TEXT NOT NULL,
|
"closing_at" DATETIME NOT NULL,
|
||||||
"buyer_id" TEXT,
|
"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_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_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,
|
"bidder_id" TEXT NOT NULL,
|
||||||
"market_offer_id" TEXT NOT NULL,
|
"market_offer_id" TEXT NOT NULL,
|
||||||
"offer_amount" INTEGER 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_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
|
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,
|
"target_user_id" TEXT,
|
||||||
"coins_amount" INTEGER,
|
"coins_amount" INTEGER,
|
||||||
"user_new_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_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
|
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,
|
"p1_new_elo" INTEGER,
|
||||||
"p2_new_elo" INTEGER,
|
"p2_new_elo" INTEGER,
|
||||||
"type" TEXT,
|
"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_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
|
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_email" TEXT,
|
||||||
"customer_name" TEXT,
|
"customer_name" TEXT,
|
||||||
"payment_status" TEXT NOT NULL,
|
"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
|
CONSTRAINT "transactions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "transactions_session_id_key" ON "transactions"("session_id");
|
CREATE UNIQUE INDEX "transactions_session_id_key" ON "transactions"("session_id");
|
||||||
|
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -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"
|
||||||
@@ -116,7 +116,7 @@ model Game {
|
|||||||
p1NewElo Int? @map("p1_new_elo")
|
p1NewElo Int? @map("p1_new_elo")
|
||||||
p2NewElo Int? @map("p2_new_elo")
|
p2NewElo Int? @map("p2_new_elo")
|
||||||
type String?
|
type String?
|
||||||
timestamp DateTime?
|
timestamp DateTime? @default(now()) @map("timestamp")
|
||||||
|
|
||||||
player1 User @relation("Player1", fields: [p1], references: [id])
|
player1 User @relation("Player1", fields: [p1], references: [id])
|
||||||
player2 User? @relation("Player2", fields: [p2], references: [id])
|
player2 User? @relation("Player2", fields: [p2], references: [id])
|
||||||
|
|||||||
@@ -102,12 +102,14 @@ export async function handleTimeoutCommand(req, res, client) {
|
|||||||
if (remaining === 0) {
|
if (remaining === 0) {
|
||||||
clearInterval(countdownInterval);
|
clearInterval(countdownInterval);
|
||||||
|
|
||||||
const votersList = (await Promise.all(poll.voters
|
const votersList = (
|
||||||
.map(async (voterId) => {
|
await Promise.all(
|
||||||
const user = await userService.getUser(voterId);
|
poll.voters.map(async (voterId) => {
|
||||||
return `- ${user?.globalName || "Utilisateur Inconnu"}`;
|
const user = await userService.getUser(voterId);
|
||||||
})
|
return `- ${user?.globalName || "Utilisateur Inconnu"}`;
|
||||||
)).join("\n");
|
}),
|
||||||
|
)
|
||||||
|
).join("\n");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await DiscordRequest(poll.endpoint, {
|
await DiscordRequest(poll.endpoint, {
|
||||||
@@ -143,12 +145,14 @@ export async function handleTimeoutCommand(req, res, client) {
|
|||||||
// --- Periodic Update Logic ---
|
// --- Periodic Update Logic ---
|
||||||
// Update the message every second with the new countdown
|
// Update the message every second with the new countdown
|
||||||
try {
|
try {
|
||||||
const votersList = (await Promise.all(poll.voters
|
const votersList = (
|
||||||
.map(async (voterId) => {
|
await Promise.all(
|
||||||
const user = await userService.getUser(voterId);
|
poll.voters.map(async (voterId) => {
|
||||||
return `- ${user?.globalName || "Utilisateur Inconnu"}`;
|
const user = await userService.getUser(voterId);
|
||||||
})
|
return `- ${user?.globalName || "Utilisateur Inconnu"}`;
|
||||||
)).join("\n");
|
}),
|
||||||
|
)
|
||||||
|
).join("\n");
|
||||||
|
|
||||||
await DiscordRequest(poll.endpoint, {
|
await DiscordRequest(poll.endpoint, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
|||||||
@@ -75,10 +75,14 @@ export async function handlePollVote(req, res) {
|
|||||||
|
|
||||||
io.emit("poll-update"); // Notify frontend clients of the change
|
io.emit("poll-update"); // Notify frontend clients of the change
|
||||||
|
|
||||||
const votersList = (await Promise.all(poll.voters.map(async (vId) => {
|
const votersList = (
|
||||||
const user = await userService.getUser(vId);
|
await Promise.all(
|
||||||
return `- ${user?.globalName || "Utilisateur Inconnu"}`;
|
poll.voters.map(async (vId) => {
|
||||||
}))).join("\n");
|
const user = await userService.getUser(vId);
|
||||||
|
return `- ${user?.globalName || "Utilisateur Inconnu"}`;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
).join("\n");
|
||||||
|
|
||||||
// --- 4. Check for Majority ---
|
// --- 4. Check for Majority ---
|
||||||
if (isVotingFor && poll.for >= poll.requiredMajority) {
|
if (isVotingFor && poll.for >= poll.requiredMajority) {
|
||||||
|
|||||||
@@ -190,22 +190,22 @@ async function handleAdminCommands(message) {
|
|||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case "?sp":
|
case "?sp":
|
||||||
let msgText = ""
|
let msgText = "";
|
||||||
for (let skinTierRank = 1; skinTierRank <= 4; skinTierRank++) {
|
for (let skinTierRank = 1; skinTierRank <= 4; skinTierRank++) {
|
||||||
msgText += `\n--- Tier Rank: ${skinTierRank} ---\n`;
|
msgText += `\n--- Tier Rank: ${skinTierRank} ---\n`;
|
||||||
let skinMaxLevels = 4;
|
let skinMaxLevels = 4;
|
||||||
let skinMaxChromas = 4;
|
let skinMaxChromas = 4;
|
||||||
for (let skinLevel = 1; skinLevel < skinMaxLevels; skinLevel++) {
|
for (let skinLevel = 1; skinLevel < skinMaxLevels; skinLevel++) {
|
||||||
msgText += (`Levels: ${skinLevel}/${skinMaxLevels}, MaxChromas: ${1}/${skinMaxChromas} - `);
|
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).successProb.toFixed(4)}, `;
|
||||||
msgText += (`${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `);
|
msgText += `${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `;
|
||||||
msgText += (`${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`);
|
msgText += `${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`;
|
||||||
}
|
}
|
||||||
for (let skinChroma = 1; skinChroma < skinMaxChromas; skinChroma++) {
|
for (let skinChroma = 1; skinChroma < skinMaxChromas; skinChroma++) {
|
||||||
msgText += (`Levels: ${skinMaxLevels}/${skinMaxLevels}, MaxChromas: ${skinChroma}/${skinMaxChromas} - `);
|
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).successProb.toFixed(4)}, `;
|
||||||
msgText += (`${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `);
|
msgText += `${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `;
|
||||||
msgText += (`${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`);
|
msgText += `${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`;
|
||||||
}
|
}
|
||||||
message.reply(msgText);
|
message.reply(msgText);
|
||||||
msgText = "";
|
msgText = "";
|
||||||
|
|||||||
@@ -361,7 +361,10 @@ export function applyAction(room, playerId, action) {
|
|||||||
if (hand.stood || hand.busted) throw new Error("Already ended");
|
if (hand.stood || hand.busted) throw new Error("Already ended");
|
||||||
hand.hasActed = true;
|
hand.hasActed = true;
|
||||||
hand.cards.push(draw(room.shoe));
|
hand.cards.push(draw(room.shoe));
|
||||||
if (isBust(hand.cards)) hand.busted = true;
|
if (isBust(hand.cards)) {
|
||||||
|
hand.busted = true;
|
||||||
|
p.activeHand++;
|
||||||
|
}
|
||||||
return "hit";
|
return "hit";
|
||||||
}
|
}
|
||||||
case "stand": {
|
case "stand": {
|
||||||
|
|||||||
@@ -97,35 +97,33 @@ export async function eloHandler(p1Id, p2Id, p1Score, p2Score, type, scores = nu
|
|||||||
|
|
||||||
if (scores) {
|
if (scores) {
|
||||||
await gameService.insertGame({
|
await gameService.insertGame({
|
||||||
id: `${p1Id}-${p2Id}-${Date.now()}`,
|
id: `${p1Id}-${p2Id}-${Date.now()}`,
|
||||||
p1: p1Id,
|
p1: p1Id,
|
||||||
p2: p2Id,
|
p2: p2Id,
|
||||||
p1Score: scores.p1,
|
p1Score: scores.p1,
|
||||||
p2Score: scores.p2,
|
p2Score: scores.p2,
|
||||||
p1Elo: p1CurrentElo,
|
p1Elo: p1CurrentElo,
|
||||||
p2Elo: p2CurrentElo,
|
p2Elo: p2CurrentElo,
|
||||||
p1NewElo: finalP1Elo,
|
p1NewElo: finalP1Elo,
|
||||||
p2NewElo: finalP2Elo,
|
p2NewElo: finalP2Elo,
|
||||||
type: type,
|
type: type,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await gameService.insertGame({
|
await gameService.insertGame({
|
||||||
id: `${p1Id}-${p2Id}-${Date.now()}`,
|
id: `${p1Id}-${p2Id}-${Date.now()}`,
|
||||||
p1: p1Id,
|
p1: p1Id,
|
||||||
p2: p2Id,
|
p2: p2Id,
|
||||||
p1Score: p1Score,
|
p1Score: p1Score,
|
||||||
p2Score: p2Score,
|
p2Score: p2Score,
|
||||||
p1Elo: p1CurrentElo,
|
p1Elo: p1CurrentElo,
|
||||||
p2Elo: p2CurrentElo,
|
p2Elo: p2CurrentElo,
|
||||||
p1NewElo: finalP1Elo,
|
p1NewElo: finalP1Elo,
|
||||||
p2NewElo: finalP2Elo,
|
p2NewElo: finalP2Elo,
|
||||||
type: type,
|
type: type,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -142,12 +140,14 @@ export async function pokerEloHandler(room) {
|
|||||||
if (playerIds.length < 2) return; // Not enough players to calculate Elo
|
if (playerIds.length < 2) return; // Not enough players to calculate Elo
|
||||||
|
|
||||||
// Fetch all players' Elo data at once
|
// Fetch all players' Elo data at once
|
||||||
const dbPlayers = await Promise.all(playerIds.map(async (id) => {
|
const dbPlayers = await Promise.all(
|
||||||
const user = await userService.getUser(id);
|
playerIds.map(async (id) => {
|
||||||
const eloData = await gameService.getUserElo(id);
|
const user = await userService.getUser(id);
|
||||||
const elo = eloData?.elo || 1000;
|
const eloData = await gameService.getUserElo(id);
|
||||||
return { ...user, elo };
|
const elo = eloData?.elo || 1000;
|
||||||
}));
|
return { ...user, elo };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const winnerIds = new Set(room.winners);
|
const winnerIds = new Set(room.winners);
|
||||||
const playerCount = dbPlayers.length;
|
const playerCount = dbPlayers.length;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { getSocketIo } from "./socket.js";
|
|||||||
import { blackjackRoutes } from "./routes/blackjack.js";
|
import { blackjackRoutes } from "./routes/blackjack.js";
|
||||||
import { marketRoutes } from "./routes/market.js";
|
import { marketRoutes } from "./routes/market.js";
|
||||||
import { monkeRoutes } from "./routes/monke.js";
|
import { monkeRoutes } from "./routes/monke.js";
|
||||||
|
import { authRoutes } from "./routes/auth.js";
|
||||||
|
|
||||||
// --- EXPRESS APP INITIALIZATION ---
|
// --- EXPRESS APP INITIALIZATION ---
|
||||||
const app = express();
|
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-Origin", FLAPI_URL);
|
||||||
res.header(
|
res.header(
|
||||||
"Access-Control-Allow-Headers",
|
"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();
|
next();
|
||||||
});
|
});
|
||||||
@@ -48,6 +49,9 @@ app.use("/public", express.static("public"));
|
|||||||
|
|
||||||
// --- API ROUTES ---
|
// --- API ROUTES ---
|
||||||
|
|
||||||
|
// Auth routes (Discord OAuth2, no client/io needed)
|
||||||
|
app.use("/api/auth", authRoutes());
|
||||||
|
|
||||||
// General API routes (users, polls, etc.)
|
// General API routes (users, polls, etc.)
|
||||||
app.use("/api", apiRoutes(client, io));
|
app.use("/api", apiRoutes(client, io));
|
||||||
|
|
||||||
|
|||||||
63
src/server/middleware/auth.js
Normal file
63
src/server/middleware/auth.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "disc
|
|||||||
import { emitDataUpdated, socketEmit, onGameOver } from "../socket.js";
|
import { emitDataUpdated, socketEmit, onGameOver } from "../socket.js";
|
||||||
import { handleCaseOpening } from "../../utils/marketNotifs.js";
|
import { handleCaseOpening } from "../../utils/marketNotifs.js";
|
||||||
import { drawCaseContent, drawCaseSkin, getSkinUpgradeProbs } from "../../utils/caseOpening.js";
|
import { drawCaseContent, drawCaseSkin, getSkinUpgradeProbs } from "../../utils/caseOpening.js";
|
||||||
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -59,8 +60,8 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/register-user", async (req, res) => {
|
router.post("/register-user", requireAuth, async (req, res) => {
|
||||||
const { discordUserId } = req.body;
|
const discordUserId = req.userId;
|
||||||
const discordUser = await client.users.fetch(discordUserId);
|
const discordUser = await client.users.fetch(discordUserId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -104,8 +105,9 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/open-case", async (req, res) => {
|
router.post("/open-case", requireAuth, async (req, res) => {
|
||||||
const { userId, caseType } = req.body;
|
const userId = req.userId;
|
||||||
|
const { caseType } = req.body;
|
||||||
|
|
||||||
let caseTypeVal;
|
let caseTypeVal;
|
||||||
switch (caseType) {
|
switch (caseType) {
|
||||||
@@ -157,15 +159,15 @@ export function apiRoutes(client, io) {
|
|||||||
);
|
);
|
||||||
const updatedSkin = await skinService.getSkin(result.randomSkinData.uuid);
|
const updatedSkin = await skinService.getSkin(result.randomSkinData.uuid);
|
||||||
await handleCaseOpening(caseType, userId, result.randomSelectedSkinUuid, client);
|
await handleCaseOpening(caseType, userId, result.randomSelectedSkinUuid, client);
|
||||||
|
|
||||||
const contentSkins = selectedSkins.map((item) => {
|
const contentSkins = selectedSkins.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
isMelee: isMeleeSkin(item.displayName),
|
isMelee: isMeleeSkin(item.displayName),
|
||||||
isVCT: isVCTSkin(item.displayName),
|
isVCT: isVCTSkin(item.displayName),
|
||||||
isChampions: isChampionsSkin(item.displayName),
|
isChampions: isChampionsSkin(item.displayName),
|
||||||
vctRegion: getVCTRegion(item.displayName),
|
vctRegion: getVCTRegion(item.displayName),
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
res.json({
|
res.json({
|
||||||
selectedSkins: contentSkins,
|
selectedSkins: contentSkins,
|
||||||
@@ -231,14 +233,12 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/skin/:uuid/instant-sell", async (req, res) => {
|
router.post("/skin/:uuid/instant-sell", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
try {
|
try {
|
||||||
const skin = await skinService.getSkin(req.params.uuid);
|
const skin = await skinService.getSkin(req.params.uuid);
|
||||||
const skinData = skins.find((s) => s.uuid === skin.uuid);
|
const skinData = skins.find((s) => s.uuid === skin.uuid);
|
||||||
if (
|
if (!skinData) {
|
||||||
!skinData
|
|
||||||
) {
|
|
||||||
return res.status(403).json({ error: "Invalid skin." });
|
return res.status(403).json({ error: "Invalid skin." });
|
||||||
}
|
}
|
||||||
if (skin.userId !== userId) {
|
if (skin.userId !== userId) {
|
||||||
@@ -248,7 +248,9 @@ export function apiRoutes(client, io) {
|
|||||||
const marketOffers = await marketService.getMarketOffersBySkin(skin.uuid);
|
const marketOffers = await marketService.getMarketOffersBySkin(skin.uuid);
|
||||||
const activeOffers = marketOffers.filter((offer) => offer.status === "pending" || offer.status === "open");
|
const activeOffers = marketOffers.filter((offer) => offer.status === "pending" || offer.status === "open");
|
||||||
if (activeOffers.length > 0) {
|
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);
|
const commandUser = await userService.getUser(userId);
|
||||||
@@ -288,27 +290,24 @@ export function apiRoutes(client, io) {
|
|||||||
const { successProb, destructionProb, upgradePrice } = getSkinUpgradeProbs(skin, skinData);
|
const { successProb, destructionProb, upgradePrice } = getSkinUpgradeProbs(skin, skinData);
|
||||||
|
|
||||||
const segments = [
|
const segments = [
|
||||||
{ id: 'SUCCEEDED', color: '5865f2', percent: successProb, label: 'Réussie' },
|
{ id: "SUCCEEDED", color: "5865f2", percent: successProb, label: "Réussie" },
|
||||||
{ id: 'DESTRUCTED', color: 'f26558', percent: destructionProb, label: 'Détruit' },
|
{ id: "DESTRUCTED", color: "f26558", percent: destructionProb, label: "Détruit" },
|
||||||
{ id: 'NONE', color: '18181818', percent: 1 - successProb - destructionProb, label: 'Échec' },
|
{ id: "NONE", color: "18181818", percent: 1 - successProb - destructionProb, label: "Échec" },
|
||||||
]
|
];
|
||||||
|
|
||||||
res.json({ segments, upgradePrice });
|
res.json({ segments, upgradePrice });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
res.status(500).json({ error: "Failed to fetch skin upgrade." });
|
res.status(500).json({ error: "Failed to fetch skin upgrade." });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/skin-upgrade/:uuid", async (req, res) => {
|
router.post("/skin-upgrade/:uuid", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
try {
|
try {
|
||||||
const skin = await skinService.getSkin(req.params.uuid);
|
const skin = await skinService.getSkin(req.params.uuid);
|
||||||
const skinData = skins.find((s) => s.uuid === skin.uuid);
|
const skinData = skins.find((s) => s.uuid === skin.uuid);
|
||||||
if (
|
if (!skinData || (skin.currentLvl >= skinData.levels.length && skin.currentChroma >= skinData.chromas.length)) {
|
||||||
!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." });
|
return res.status(403).json({ error: "Skin is already maxed out or invalid skin." });
|
||||||
}
|
}
|
||||||
if (skin.userId !== userId) {
|
if (skin.userId !== userId) {
|
||||||
@@ -328,7 +327,7 @@ export function apiRoutes(client, io) {
|
|||||||
if (commandUser.coins < upgradePrice) {
|
if (commandUser.coins < upgradePrice) {
|
||||||
return res.status(403).json({ error: `Pas assez de FlopoCoins (${upgradePrice} requis).` });
|
return res.status(403).json({ error: `Pas assez de FlopoCoins (${upgradePrice} requis).` });
|
||||||
}
|
}
|
||||||
|
|
||||||
await logService.insertLog({
|
await logService.insertLog({
|
||||||
id: `${userId}-${Date.now()}`,
|
id: `${userId}-${Date.now()}`,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@@ -341,7 +340,7 @@ export function apiRoutes(client, io) {
|
|||||||
|
|
||||||
let succeeded = false;
|
let succeeded = false;
|
||||||
let destructed = false;
|
let destructed = false;
|
||||||
|
|
||||||
const roll = Math.random();
|
const roll = Math.random();
|
||||||
if (roll < destructionProb) {
|
if (roll < destructionProb) {
|
||||||
destructed = true;
|
destructed = true;
|
||||||
@@ -363,7 +362,7 @@ export function apiRoutes(client, io) {
|
|||||||
return parseFloat(result.toFixed(0));
|
return parseFloat(result.toFixed(0));
|
||||||
};
|
};
|
||||||
skin.currentPrice = calculatePrice();
|
skin.currentPrice = calculatePrice();
|
||||||
|
|
||||||
await skinService.updateSkin({
|
await skinService.updateSkin({
|
||||||
uuid: skin.uuid,
|
uuid: skin.uuid,
|
||||||
userId: skin.userId,
|
userId: skin.userId,
|
||||||
@@ -380,8 +379,10 @@ export function apiRoutes(client, io) {
|
|||||||
currentPrice: null,
|
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" });
|
res.json({ wonId: succeeded ? "SUCCEEDED" : destructed ? "DESTRUCTED" : "NONE" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching skin upgrade:", error);
|
console.error("Error fetching skin upgrade:", error);
|
||||||
@@ -470,7 +471,7 @@ export function apiRoutes(client, io) {
|
|||||||
try {
|
try {
|
||||||
const games = await gameService.getUserGames(req.params.id);
|
const games = await gameService.getUserGames(req.params.id);
|
||||||
const eloHistory = games
|
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)
|
.filter((game) => game.p2 !== null)
|
||||||
.map((game) => (game.p1 === req.params.id ? game.p1NewElo : game.p2NewElo));
|
.map((game) => (game.p1 === req.params.id ? game.p1NewElo : game.p2NewElo));
|
||||||
eloHistory.splice(0, 0, 1000);
|
eloHistory.splice(0, 0, 1000);
|
||||||
@@ -489,7 +490,7 @@ export function apiRoutes(client, io) {
|
|||||||
offer.skin = await skinService.getSkin(offer.skinUuid);
|
offer.skin = await skinService.getSkin(offer.skinUuid);
|
||||||
offer.seller = await userService.getUser(offer.sellerId);
|
offer.seller = await userService.getUser(offer.sellerId);
|
||||||
offer.buyer = offer.buyerId ? await userService.getUser(offer.buyerId) : null;
|
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) {
|
for (const bid of offer.bids) {
|
||||||
bid.bidder = await userService.getUser(bid.bidderId);
|
bid.bidder = await userService.getUser(bid.bidderId);
|
||||||
}
|
}
|
||||||
@@ -509,15 +510,18 @@ export function apiRoutes(client, io) {
|
|||||||
|
|
||||||
router.get("/user/:id/games-history", async (req, res) => {
|
router.get("/user/:id/games-history", async (req, res) => {
|
||||||
try {
|
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 });
|
res.json({ games });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ error: "Failed to fetch games history." });
|
res.status(500).json({ error: "Failed to fetch games history." });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/user/:id/daily", async (req, res) => {
|
router.get("/user/:id/daily", requireAuth, async (req, res) => {
|
||||||
const { id } = req.params;
|
const id = req.userId;
|
||||||
try {
|
try {
|
||||||
const akhy = await userService.getUser(id);
|
const akhy = await userService.getUser(id);
|
||||||
if (!akhy) return res.status(404).json({ message: "Utilisateur introuvable" });
|
if (!akhy) return res.status(404).json({ message: "Utilisateur introuvable" });
|
||||||
@@ -549,9 +553,9 @@ export function apiRoutes(client, io) {
|
|||||||
res.json({ activePolls });
|
res.json({ activePolls });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/timedout", async (req, res) => {
|
router.post("/timedout", requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||||
const member = await guild.members.fetch(userId);
|
const member = await guild.members.fetch(userId);
|
||||||
res.status(200).json({ isTimedOut: member?.isCommunicationDisabled() || false });
|
res.status(200).json({ isTimedOut: member?.isCommunicationDisabled() || false });
|
||||||
@@ -562,8 +566,9 @@ export function apiRoutes(client, io) {
|
|||||||
|
|
||||||
// --- Shop & Interaction Routes ---
|
// --- Shop & Interaction Routes ---
|
||||||
|
|
||||||
router.post("/change-nickname", async (req, res) => {
|
router.post("/change-nickname", requireAuth, async (req, res) => {
|
||||||
const { userId, nickname, commandUserId } = req.body;
|
const { userId, nickname } = req.body;
|
||||||
|
const commandUserId = req.userId;
|
||||||
const commandUser = await userService.getUser(commandUserId);
|
const commandUser = await userService.getUser(commandUserId);
|
||||||
if (!commandUser) return res.status(404).json({ message: "Command user not found." });
|
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)." });
|
if (commandUser.coins < 1000) return res.status(403).json({ message: "Pas assez de FlopoCoins (1000 requis)." });
|
||||||
@@ -612,8 +617,9 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/spam-ping", async (req, res) => {
|
router.post("/spam-ping", requireAuth, async (req, res) => {
|
||||||
const { userId, commandUserId } = req.body;
|
const { userId } = req.body;
|
||||||
|
const commandUserId = req.userId;
|
||||||
|
|
||||||
const user = await userService.getUser(userId);
|
const user = await userService.getUser(userId);
|
||||||
const commandUser = await userService.getUser(commandUserId);
|
const commandUser = await userService.getUser(commandUserId);
|
||||||
@@ -669,8 +675,9 @@ export function apiRoutes(client, io) {
|
|||||||
res.status(200).json({ slowmodes: activeSlowmodes });
|
res.status(200).json({ slowmodes: activeSlowmodes });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/slowmode", async (req, res) => {
|
router.post("/slowmode", requireAuth, async (req, res) => {
|
||||||
let { userId, commandUserId } = req.body;
|
let { userId } = req.body;
|
||||||
|
const commandUserId = req.userId;
|
||||||
|
|
||||||
const user = await userService.getUser(userId);
|
const user = await userService.getUser(userId);
|
||||||
const commandUser = await userService.getUser(commandUserId);
|
const commandUser = await userService.getUser(commandUserId);
|
||||||
@@ -759,8 +766,9 @@ export function apiRoutes(client, io) {
|
|||||||
|
|
||||||
// --- Time-Out Route ---
|
// --- Time-Out Route ---
|
||||||
|
|
||||||
router.post("/timeout", async (req, res) => {
|
router.post("/timeout", requireAuth, async (req, res) => {
|
||||||
let { userId, commandUserId } = req.body;
|
let { userId } = req.body;
|
||||||
|
const commandUserId = req.userId;
|
||||||
|
|
||||||
const user = await userService.getUser(userId);
|
const user = await userService.getUser(userId);
|
||||||
const commandUser = await userService.getUser(commandUserId);
|
const commandUser = await userService.getUser(commandUserId);
|
||||||
@@ -875,8 +883,9 @@ export function apiRoutes(client, io) {
|
|||||||
res.status(200).json({ predis: reversedPredis });
|
res.status(200).json({ predis: reversedPredis });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/start-predi", async (req, res) => {
|
router.post("/start-predi", requireAuth, async (req, res) => {
|
||||||
let { commandUserId, label, options, closingTime, payoutTime } = req.body;
|
let { label, options, closingTime, payoutTime } = req.body;
|
||||||
|
const commandUserId = req.userId;
|
||||||
|
|
||||||
const commandUser = await userService.getUser(commandUserId);
|
const commandUser = await userService.getUser(commandUserId);
|
||||||
|
|
||||||
@@ -971,8 +980,9 @@ export function apiRoutes(client, io) {
|
|||||||
return res.status(200).json({ message: `Ta prédi '${label}' a commencée !` });
|
return res.status(200).json({ message: `Ta prédi '${label}' a commencée !` });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/vote-predi", async (req, res) => {
|
router.post("/vote-predi", requireAuth, async (req, res) => {
|
||||||
const { commandUserId, predi, amount, option } = req.body;
|
const { predi, amount, option } = req.body;
|
||||||
|
const commandUserId = req.userId;
|
||||||
|
|
||||||
let warning = false;
|
let warning = false;
|
||||||
|
|
||||||
@@ -1039,8 +1049,9 @@ export function apiRoutes(client, io) {
|
|||||||
return res.status(200).send({ message: `Vote enregistré!` });
|
return res.status(200).send({ message: `Vote enregistré!` });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/end-predi", async (req, res) => {
|
router.post("/end-predi", requireAuth, async (req, res) => {
|
||||||
const { commandUserId, predi, confirm, winningOption } = req.body;
|
const { predi, confirm, winningOption } = req.body;
|
||||||
|
const commandUserId = req.userId;
|
||||||
|
|
||||||
const commandUser = await userService.getUser(commandUserId);
|
const commandUser = await userService.getUser(commandUserId);
|
||||||
if (!commandUser) return res.status(403).send({ message: "Oups, je ne te connais pas" });
|
if (!commandUser) return res.status(403).send({ message: "Oups, je ne te connais pas" });
|
||||||
@@ -1154,13 +1165,14 @@ export function apiRoutes(client, io) {
|
|||||||
return res.status(200).json({ message: "Prédi close" });
|
return res.status(200).json({ message: "Prédi close" });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/snake/reward", async (req, res) => {
|
router.post("/snake/reward", requireAuth, async (req, res) => {
|
||||||
const { discordId, score, isWin } = req.body;
|
const discordId = req.userId;
|
||||||
|
const { score, isWin } = req.body;
|
||||||
console.log(`[SNAKE][SOLO]${discordId}: score=${score}, isWin=${isWin}`);
|
console.log(`[SNAKE][SOLO]${discordId}: score=${score}, isWin=${isWin}`);
|
||||||
try {
|
try {
|
||||||
const user = await userService.getUser(discordId);
|
const user = await userService.getUser(discordId);
|
||||||
if (!user) return res.status(404).json({ message: "Utilisateur introuvable" });
|
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;
|
const newCoins = user.coins + reward;
|
||||||
await userService.updateUserCoins(discordId, newCoins);
|
await userService.updateUserCoins(discordId, newCoins);
|
||||||
await logService.insertLog({
|
await logService.insertLog({
|
||||||
@@ -1179,23 +1191,23 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/queue/leave", async (req, res) => {
|
router.post("/queue/leave", requireAuth, async (req, res) => {
|
||||||
const { discordId, game, reason } = req.body;
|
const discordId = req.userId;
|
||||||
|
const { game, reason } = req.body;
|
||||||
if (game === "snake" && (reason === "beforeunload" || reason === "route-leave")) {
|
if (game === "snake" && (reason === "beforeunload" || reason === "route-leave")) {
|
||||||
|
|
||||||
const lobby = Object.values(activeSnakeGames).find(
|
const lobby = Object.values(activeSnakeGames).find(
|
||||||
(l) => (l.p1.id === discordId || l.p2.id === discordId) && !l.gameOver,
|
(l) => (l.p1.id === discordId || l.p2.id === discordId) && !l.gameOver,
|
||||||
);
|
);
|
||||||
if (!lobby) return;
|
if (!lobby) return;
|
||||||
|
|
||||||
const player = lobby.p1.id === discordId ? lobby.p1 : lobby.p2;
|
const player = lobby.p1.id === discordId ? lobby.p1 : lobby.p2;
|
||||||
const otherPlayer = lobby.p1.id === discordId ? lobby.p2 : lobby.p1;
|
const otherPlayer = lobby.p1.id === discordId ? lobby.p2 : lobby.p1;
|
||||||
if (player.gameOver === true) return res.status(200).json({ message: "Déjà quitté" });
|
if (player.gameOver === true) return res.status(200).json({ message: "Déjà quitté" });
|
||||||
player.gameOver = true;
|
player.gameOver = true;
|
||||||
otherPlayer.win = true;
|
otherPlayer.win = true;
|
||||||
|
|
||||||
lobby.lastmove = Date.now();
|
lobby.lastmove = Date.now();
|
||||||
|
|
||||||
// Broadcast the updated state to both players
|
// Broadcast the updated state to both players
|
||||||
await socketEmit("snakegamestate", {
|
await socketEmit("snakegamestate", {
|
||||||
lobby: {
|
lobby: {
|
||||||
@@ -1203,7 +1215,7 @@ export function apiRoutes(client, io) {
|
|||||||
p2: lobby.p2,
|
p2: lobby.p2,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if game should end
|
// Check if game should end
|
||||||
if (lobby.p1.gameOver && lobby.p2.gameOver) {
|
if (lobby.p1.gameOver && lobby.p2.gameOver) {
|
||||||
// Both players finished - determine winner
|
// Both players finished - determine winner
|
||||||
@@ -1239,11 +1251,12 @@ export function apiRoutes(client, io) {
|
|||||||
res.json({ offers: COIN_OFFERS });
|
res.json({ offers: COIN_OFFERS });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/create-checkout-session", async (req, res) => {
|
router.post("/create-checkout-session", requireAuth, async (req, res) => {
|
||||||
const { userId, offerId } = req.body;
|
const userId = req.userId;
|
||||||
|
const { offerId } = req.body;
|
||||||
|
|
||||||
if (!userId || !offerId) {
|
if (!offerId) {
|
||||||
return res.status(400).json({ error: "Missing required fields: userId, offerId" });
|
return res.status(400).json({ error: "Missing required field: offerId" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const offer = COIN_OFFERS.find((o) => o.id === offerId);
|
const offer = COIN_OFFERS.find((o) => o.id === offerId);
|
||||||
@@ -1261,11 +1274,11 @@ export function apiRoutes(client, io) {
|
|||||||
const FLAPI_URL = process.env.DEV_SITE === "true" ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL;
|
const FLAPI_URL = process.env.DEV_SITE === "true" ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL;
|
||||||
|
|
||||||
const session = await stripe.checkout.sessions.create({
|
const session = await stripe.checkout.sessions.create({
|
||||||
payment_method_types: ['card'],
|
payment_method_types: ["card"],
|
||||||
line_items: [
|
line_items: [
|
||||||
{
|
{
|
||||||
price_data: {
|
price_data: {
|
||||||
currency: 'eur',
|
currency: "eur",
|
||||||
product_data: {
|
product_data: {
|
||||||
name: offer.label,
|
name: offer.label,
|
||||||
description: `Achat de ${offer.label} pour FlopoBot`,
|
description: `Achat de ${offer.label} pour FlopoBot`,
|
||||||
@@ -1275,7 +1288,7 @@ export function apiRoutes(client, io) {
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
mode: 'payment',
|
mode: "payment",
|
||||||
success_url: `${FLAPI_URL}/payment-success?session_id={CHECKOUT_SESSION_ID}`,
|
success_url: `${FLAPI_URL}/payment-success?session_id={CHECKOUT_SESSION_ID}`,
|
||||||
cancel_url: `${FLAPI_URL}/dashboard`,
|
cancel_url: `${FLAPI_URL}/dashboard`,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1284,9 +1297,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) {
|
} catch (error) {
|
||||||
console.error("Error creating checkout session:", error);
|
console.error("Error creating checkout session:", error);
|
||||||
res.status(500).json({ error: "Failed to create checkout session" });
|
res.status(500).json({ error: "Failed to create checkout session" });
|
||||||
@@ -1294,7 +1309,7 @@ export function apiRoutes(client, io) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post("/buy-coins", async (req, res) => {
|
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;
|
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
||||||
|
|
||||||
if (!endpointSecret) {
|
if (!endpointSecret) {
|
||||||
@@ -1303,7 +1318,7 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let event;
|
let event;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Verify webhook signature - requires raw body
|
// Verify webhook signature - requires raw body
|
||||||
// Note: You need to configure Express to preserve raw body for this route
|
// Note: You need to configure Express to preserve raw body for this route
|
||||||
@@ -1315,9 +1330,9 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle the event
|
// Handle the event
|
||||||
if (event.type === 'checkout.session.completed') {
|
if (event.type === "checkout.session.completed") {
|
||||||
const session = event.data.object;
|
const session = event.data.object;
|
||||||
|
|
||||||
// Extract metadata from the checkout session
|
// Extract metadata from the checkout session
|
||||||
const commandUserId = session.metadata?.userId;
|
const commandUserId = session.metadata?.userId;
|
||||||
const expectedCoins = parseInt(session.metadata?.coins);
|
const expectedCoins = parseInt(session.metadata?.coins);
|
||||||
@@ -1325,7 +1340,7 @@ export function apiRoutes(client, io) {
|
|||||||
const currency = session.currency;
|
const currency = session.currency;
|
||||||
const customerEmail = session.customer_details?.email;
|
const customerEmail = session.customer_details?.email;
|
||||||
const customerName = session.customer_details?.name;
|
const customerName = session.customer_details?.name;
|
||||||
|
|
||||||
// Validate metadata exists
|
// Validate metadata exists
|
||||||
if (!commandUserId || !expectedCoins) {
|
if (!commandUserId || !expectedCoins) {
|
||||||
console.error("Missing userId or coins in session metadata");
|
console.error("Missing userId or coins in session metadata");
|
||||||
@@ -1333,7 +1348,7 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify payment was successful
|
// Verify payment was successful
|
||||||
if (session.payment_status !== 'paid') {
|
if (session.payment_status !== "paid") {
|
||||||
console.error(`Payment not completed for session ${session.id}`);
|
console.error(`Payment not completed for session ${session.id}`);
|
||||||
return res.status(400).json({ error: "Payment not completed" });
|
return res.status(400).json({ error: "Payment not completed" });
|
||||||
}
|
}
|
||||||
@@ -1380,12 +1395,16 @@ export function apiRoutes(client, io) {
|
|||||||
userNewAmount: newCoins,
|
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
|
// Notify user via Discord if possible
|
||||||
try {
|
try {
|
||||||
const discordUser = await client.users.fetch(commandUserId);
|
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) {
|
} catch (e) {
|
||||||
console.log(`Could not DM user ${commandUserId}:`, e.message);
|
console.log(`Could not DM user ${commandUserId}:`, e.message);
|
||||||
}
|
}
|
||||||
|
|||||||
116
src/server/routes/auth.js
Normal file
116
src/server/routes/auth.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import * as logService from "../../services/log.service.js";
|
|||||||
import { client } from "../../bot/client.js";
|
import { client } from "../../bot/client.js";
|
||||||
import { emitToast, emitUpdate, emitPlayerUpdate } from "../socket.js";
|
import { emitToast, emitUpdate, emitPlayerUpdate } from "../socket.js";
|
||||||
import { EmbedBuilder, time } from "discord.js";
|
import { EmbedBuilder, time } from "discord.js";
|
||||||
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
|
|
||||||
export function blackjackRoutes(io) {
|
export function blackjackRoutes(io) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -80,25 +81,23 @@ export function blackjackRoutes(io) {
|
|||||||
for (const p of Object.values(room.players)) {
|
for (const p of Object.values(room.players)) {
|
||||||
try {
|
try {
|
||||||
if (!p.inRound) continue;
|
if (!p.inRound) continue;
|
||||||
const h = p.hands[p.activeHand];
|
// Handle all remaining hands (important after splits)
|
||||||
if (h && !h.hasActed && !h.busted && !h.stood && !h.surrendered) {
|
for (let i = p.activeHand; i < p.hands.length; i++) {
|
||||||
h.surrendered = true;
|
const h = p.hands[i];
|
||||||
|
if (!h || h.busted || h.stood || h.surrendered) continue;
|
||||||
h.stood = true;
|
h.stood = true;
|
||||||
h.hasActed = true;
|
h.hasActed = true;
|
||||||
//room.leavingAfterRound[p.id] = true; // kick at end of round
|
|
||||||
emitToast({ type: "player-timeout", userId: p.id });
|
|
||||||
changed = true;
|
changed = true;
|
||||||
} else if (h && h.hasActed && !h.stood) {
|
}
|
||||||
h.stood = true;
|
if (changed) {
|
||||||
//room.leavingAfterRound[p.id] = true; // kick at end of round
|
p.activeHand = p.hands.length;
|
||||||
emitToast({ type: "player-auto-stand", userId: p.id });
|
emitToast({ type: "player-auto-stand", userId: p.id });
|
||||||
changed = true;
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changed) emitUpdate("auto-surrender", snapshot(room));
|
//if (changed) emitUpdate("auto-surrender", snapshot(room));
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,9 +122,8 @@ export function blackjackRoutes(io) {
|
|||||||
// --- Public endpoints ---
|
// --- Public endpoints ---
|
||||||
router.get("/", (req, res) => res.status(200).json({ room: snapshot(room) }));
|
router.get("/", (req, res) => res.status(200).json({ room: snapshot(room) }));
|
||||||
|
|
||||||
router.post("/join", async (req, res) => {
|
router.post("/join", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
if (!userId) return res.status(400).json({ message: "userId required" });
|
|
||||||
if (room.players[userId]) return res.status(200).json({ message: "Already here" });
|
if (room.players[userId]) return res.status(200).json({ message: "Already here" });
|
||||||
|
|
||||||
const user = await client.users.fetch(userId);
|
const user = await client.users.fetch(userId);
|
||||||
@@ -183,13 +181,17 @@ export function blackjackRoutes(io) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emitUpdate("player-joined", snapshot(room));
|
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" });
|
return res.status(200).json({ message: "joined" });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/leave", async (req, res) => {
|
router.post("/leave", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
if (!userId || !room.players[userId]) return res.status(403).json({ message: "not in room" });
|
if (!room.players[userId]) return res.status(403).json({ message: "not in room" });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||||
@@ -225,13 +227,18 @@ export function blackjackRoutes(io) {
|
|||||||
delete room.players[userId];
|
delete room.players[userId];
|
||||||
emitUpdate("player-left", snapshot(room));
|
emitUpdate("player-left", snapshot(room));
|
||||||
const user = await client.users.fetch(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(),
|
||||||
|
});
|
||||||
return res.status(200).json({ message: "left" });
|
return res.status(200).json({ message: "left" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/bet", async (req, res) => {
|
router.post("/bet", requireAuth, async (req, res) => {
|
||||||
const { userId, amount } = req.body;
|
const userId = req.userId;
|
||||||
|
const { amount } = req.body;
|
||||||
const p = room.players[userId];
|
const p = room.players[userId];
|
||||||
if (!p) return res.status(404).json({ message: "not in room" });
|
if (!p) return res.status(404).json({ message: "not in room" });
|
||||||
if (room.status !== "betting") return res.status(403).json({ message: "betting-closed" });
|
if (room.status !== "betting") return res.status(403).json({ message: "betting-closed" });
|
||||||
@@ -262,8 +269,8 @@ export function blackjackRoutes(io) {
|
|||||||
return res.status(200).json({ message: "bet-accepted" });
|
return res.status(200).json({ message: "bet-accepted" });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/action/:action", async (req, res) => {
|
router.post("/action/:action", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
const action = req.params.action;
|
const action = req.params.action;
|
||||||
const p = room.players[userId];
|
const p = room.players[userId];
|
||||||
if (!p) return res.status(404).json({ message: "not in room" });
|
if (!p) return res.status(404).json({ message: "not in room" });
|
||||||
@@ -361,7 +368,11 @@ export function blackjackRoutes(io) {
|
|||||||
for (const userId of Object.keys(room.leavingAfterRound)) {
|
for (const userId of Object.keys(room.leavingAfterRound)) {
|
||||||
delete room.players[userId];
|
delete room.players[userId];
|
||||||
const user = await client.users.fetch(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
|
// Prepare next round
|
||||||
startBetting(room, now);
|
startBetting(room, now);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as logService from "../../services/log.service.js";
|
|||||||
import * as marketService from "../../services/market.service.js";
|
import * as marketService from "../../services/market.service.js";
|
||||||
import { emitMarketUpdate } from "../socket.js";
|
import { emitMarketUpdate } from "../socket.js";
|
||||||
import { handleNewMarketOffer, handleNewMarketOfferBid } from "../../utils/marketNotifs.js";
|
import { handleNewMarketOffer, handleNewMarketOfferBid } from "../../utils/marketNotifs.js";
|
||||||
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -63,8 +64,9 @@ export function marketRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/place-offer", async (req, res) => {
|
router.post("/place-offer", requireAuth, async (req, res) => {
|
||||||
const { seller_id, skin_uuid, starting_price, delay, duration, timestamp } = req.body;
|
const seller_id = req.userId;
|
||||||
|
const { skin_uuid, starting_price, delay, duration, timestamp } = req.body;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
try {
|
try {
|
||||||
const skin = await skinService.getSkin(skin_uuid);
|
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) => {
|
router.post("/offers/:id/place-bid", requireAuth, async (req, res) => {
|
||||||
const { buyer_id, bid_amount, timestamp } = req.body;
|
const buyer_id = req.userId;
|
||||||
|
const { bid_amount, timestamp } = req.body;
|
||||||
try {
|
try {
|
||||||
const offer = await marketService.getMarketOfferById(req.params.id);
|
const offer = await marketService.getMarketOfferById(req.params.id);
|
||||||
if (!offer) return res.status(404).send({ error: "Offer not found" });
|
if (!offer) return res.status(404).send({ error: "Offer not found" });
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { socketEmit } from "../socket.js";
|
|||||||
import * as userService from "../../services/user.service.js";
|
import * as userService from "../../services/user.service.js";
|
||||||
import * as logService from "../../services/log.service.js";
|
import * as logService from "../../services/log.service.js";
|
||||||
import { init } from "openai/_shims/index.mjs";
|
import { init } from "openai/_shims/index.mjs";
|
||||||
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -29,11 +30,9 @@ export function monkeRoutes(client, io) {
|
|||||||
return res.status(200).json({ userGamePath });
|
return res.status(200).json({ userGamePath });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/:userId/start", async (req, res) => {
|
router.post("/:userId/start", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.params;
|
const userId = req.userId;
|
||||||
const { initialBet } = req.body;
|
const { initialBet } = req.body;
|
||||||
|
|
||||||
if (!userId) return res.status(400).json({ error: "User ID is required" });
|
|
||||||
const user = await userService.getUser(userId);
|
const user = await userService.getUser(userId);
|
||||||
if (!user) return res.status(404).json({ error: "User not found" });
|
if (!user) return res.status(404).json({ error: "User not found" });
|
||||||
if (!initialBet) return res.status(400).json({ error: "Initial bet is required" });
|
if (!initialBet) return res.status(400).json({ error: "Initial bet is required" });
|
||||||
@@ -54,23 +53,24 @@ export function monkeRoutes(client, io) {
|
|||||||
return res.status(500).json({ error: "Failed to update user coins" });
|
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] });
|
return res.status(200).json({ message: "Monke game started", userGamePath: monkePaths[userId] });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/:userId/play", async (req, res) => {
|
router.post("/:userId/play", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.params;
|
const userId = req.userId;
|
||||||
const { choice, step } = req.body;
|
const { choice, step } = req.body;
|
||||||
|
|
||||||
if (!userId) return res.status(400).json({ error: "User ID is required" });
|
|
||||||
const user = await userService.getUser(userId);
|
const user = await userService.getUser(userId);
|
||||||
if (!user) return res.status(404).json({ error: "User not found" });
|
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" });
|
if (!monkePaths[userId]) return res.status(400).json({ error: "No active game found for this user" });
|
||||||
|
|
||||||
const currentRound = monkePaths[userId].length - 1;
|
const currentRound = monkePaths[userId].length - 1;
|
||||||
if (step !== currentRound) return res.status(400).json({ error: "Invalid step for the current round" });
|
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
|
const randomLoseChoice = Math.floor(Math.random() * 3); // 0, 1, or 2
|
||||||
|
|
||||||
if (choice !== randomLoseChoice) {
|
if (choice !== randomLoseChoice) {
|
||||||
@@ -79,7 +79,14 @@ export function monkeRoutes(client, io) {
|
|||||||
monkePaths[userId][currentRound].extractValue = Math.round(monkePaths[userId][currentRound].bet * 1.33);
|
monkePaths[userId][currentRound].extractValue = Math.round(monkePaths[userId][currentRound].bet * 1.33);
|
||||||
monkePaths[userId][currentRound].timestamp = Date.now();
|
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 });
|
return res.status(200).json({ message: "Round won", userGamePath: monkePaths[userId], lost: false });
|
||||||
} else {
|
} else {
|
||||||
@@ -95,9 +102,8 @@ export function monkeRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/:userId/stop", async (req, res) => {
|
router.post("/:userId/stop", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.params;
|
const userId = req.userId;
|
||||||
if (!userId) return res.status(400).json({ error: "User ID is required" });
|
|
||||||
const user = await userService.getUser(userId);
|
const user = await userService.getUser(userId);
|
||||||
if (!user) return res.status(404).json({ error: "User not found" });
|
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" });
|
if (!monkePaths[userId]) return res.status(400).json({ error: "No active game found for this user" });
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { client } from "../../bot/client.js";
|
|||||||
import { emitPokerToast, emitPokerUpdate } from "../socket.js";
|
import { emitPokerToast, emitPokerUpdate } from "../socket.js";
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||||
import { formatAmount } from "../../utils/index.js";
|
import { formatAmount } from "../../utils/index.js";
|
||||||
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
|
|
||||||
const { Hand } = pkg;
|
const { Hand } = pkg;
|
||||||
|
|
||||||
@@ -44,9 +45,9 @@ export function pokerRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/create", async (req, res) => {
|
router.post("/create", requireAuth, async (req, res) => {
|
||||||
const { creatorId, minBet, fakeMoney } = req.body;
|
const creatorId = req.userId;
|
||||||
if (!creatorId) return res.status(400).json({ message: "Creator ID is required." });
|
const { minBet, fakeMoney } = req.body;
|
||||||
|
|
||||||
if (Object.values(pokerRooms).some((room) => room.host_id === creatorId || room.players[creatorId])) {
|
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." });
|
return res.status(403).json({ message: "You are already in a poker room." });
|
||||||
@@ -125,14 +126,18 @@ export function pokerRoutes(client, io) {
|
|||||||
res.status(201).json({ roomId: id });
|
res.status(201).json({ roomId: id });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/join", async (req, res) => {
|
router.post("/join", requireAuth, async (req, res) => {
|
||||||
const { userId, roomId } = req.body;
|
const userId = req.userId;
|
||||||
if (!userId || !roomId) return res.status(400).json({ message: "User ID and Room ID are required." });
|
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 (!pokerRooms[roomId]) return res.status(404).json({ message: "Room not found." });
|
||||||
if (Object.values(pokerRooms).some((r) => r.players[userId] || r.queue[userId])) {
|
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." });
|
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." });
|
return res.status(403).json({ message: "You do not have enough coins to join this room." });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +145,9 @@ export function pokerRoutes(client, io) {
|
|||||||
res.status(200).json({ message: "Successfully joined." });
|
res.status(200).json({ message: "Successfully joined." });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/accept", async (req, res) => {
|
router.post("/accept", requireAuth, async (req, res) => {
|
||||||
const { hostId, playerId, roomId } = req.body;
|
const hostId = req.userId;
|
||||||
|
const { playerId, roomId } = req.body;
|
||||||
const room = pokerRooms[roomId];
|
const room = pokerRooms[roomId];
|
||||||
if (!room || room.host_id !== hostId || !room.queue[playerId]) {
|
if (!room || room.host_id !== hostId || !room.queue[playerId]) {
|
||||||
return res.status(403).json({ message: "Unauthorized or player not in queue." });
|
return res.status(403).json({ message: "Unauthorized or player not in queue." });
|
||||||
@@ -169,8 +175,9 @@ export function pokerRoutes(client, io) {
|
|||||||
res.status(200).json({ message: "Player accepted." });
|
res.status(200).json({ message: "Player accepted." });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/leave", async (req, res) => {
|
router.post("/leave", requireAuth, async (req, res) => {
|
||||||
const { userId, roomId } = req.body;
|
const userId = req.userId;
|
||||||
|
const { roomId } = req.body;
|
||||||
|
|
||||||
if (!pokerRooms[roomId]) return res.status(404).send({ message: "Table introuvable" });
|
if (!pokerRooms[roomId]) return res.status(404).send({ message: "Table introuvable" });
|
||||||
if (!pokerRooms[roomId].players[userId]) return res.status(404).send({ message: "Joueur introuvable" });
|
if (!pokerRooms[roomId].players[userId]) return res.status(404).send({ message: "Joueur introuvable" });
|
||||||
@@ -220,8 +227,9 @@ export function pokerRoutes(client, io) {
|
|||||||
return res.status(200);
|
return res.status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/kick", async (req, res) => {
|
router.post("/kick", requireAuth, async (req, res) => {
|
||||||
const { commandUserId, userId, roomId } = req.body;
|
const commandUserId = req.userId;
|
||||||
|
const { userId, roomId } = req.body;
|
||||||
|
|
||||||
if (!pokerRooms[roomId]) return res.status(404).send({ message: "Table introuvable" });
|
if (!pokerRooms[roomId]) return res.status(404).send({ message: "Table introuvable" });
|
||||||
if (!pokerRooms[roomId].players[commandUserId]) return res.status(404).send({ message: "Joueur introuvable" });
|
if (!pokerRooms[roomId].players[commandUserId]) return res.status(404).send({ message: "Joueur introuvable" });
|
||||||
@@ -262,7 +270,7 @@ export function pokerRoutes(client, io) {
|
|||||||
|
|
||||||
// --- Game Action Endpoints ---
|
// --- Game Action Endpoints ---
|
||||||
|
|
||||||
router.post("/start", async (req, res) => {
|
router.post("/start", requireAuth, async (req, res) => {
|
||||||
const { roomId } = req.body;
|
const { roomId } = req.body;
|
||||||
const room = pokerRooms[roomId];
|
const room = pokerRooms[roomId];
|
||||||
if (!room) return res.status(404).json({ message: "Room not found." });
|
if (!room) return res.status(404).json({ message: "Room not found." });
|
||||||
@@ -273,7 +281,7 @@ export function pokerRoutes(client, io) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// NEW: Endpoint to start the next hand
|
// 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 { roomId } = req.body;
|
||||||
const room = pokerRooms[roomId];
|
const room = pokerRooms[roomId];
|
||||||
if (!room || !room.waiting_for_restart) {
|
if (!room || !room.waiting_for_restart) {
|
||||||
@@ -283,8 +291,9 @@ export function pokerRoutes(client, io) {
|
|||||||
res.status(200).json({ message: "Next hand started." });
|
res.status(200).json({ message: "Next hand started." });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/action/:action", async (req, res) => {
|
router.post("/action/:action", requireAuth, async (req, res) => {
|
||||||
const { playerId, amount, roomId } = req.body;
|
const playerId = req.userId;
|
||||||
|
const { amount, roomId } = req.body;
|
||||||
const { action } = req.params;
|
const { action } = req.params;
|
||||||
const room = pokerRooms[roomId];
|
const room = pokerRooms[roomId];
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import * as userService from "../../services/user.service.js";
|
|||||||
import * as logService from "../../services/log.service.js";
|
import * as logService from "../../services/log.service.js";
|
||||||
import * as solitaireService from "../../services/solitaire.service.js";
|
import * as solitaireService from "../../services/solitaire.service.js";
|
||||||
import { socketEmit } from "../socket.js";
|
import { socketEmit } from "../socket.js";
|
||||||
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -36,9 +37,9 @@ const router = express.Router();
|
|||||||
export function solitaireRoutes(client, io) {
|
export function solitaireRoutes(client, io) {
|
||||||
// --- Game Initialization Endpoints ---
|
// --- Game Initialization Endpoints ---
|
||||||
|
|
||||||
router.post("/start", (req, res) => {
|
router.post("/start", requireAuth, (req, res) => {
|
||||||
const { userId, userSeed, hardMode } = req.body;
|
const userId = req.userId;
|
||||||
if (!userId) return res.status(400).json({ error: "User ID is required." });
|
const { userSeed, hardMode } = req.body;
|
||||||
|
|
||||||
// If a game already exists for the user, return it instead of creating a new one.
|
// If a game already exists for the user, return it instead of creating a new one.
|
||||||
if (activeSolitaireGames[userId] && !activeSolitaireGames[userId].isSOTD) {
|
if (activeSolitaireGames[userId] && !activeSolitaireGames[userId].isSOTD) {
|
||||||
@@ -78,11 +79,11 @@ export function solitaireRoutes(client, io) {
|
|||||||
res.json({ success: true, gameState });
|
res.json({ success: true, gameState });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/start/sotd", async (req, res) => {
|
router.post("/start/sotd", requireAuth, async (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
/*if (!userId || !getUser.get(userId)) {
|
/*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) {
|
if (activeSolitaireGames[userId]?.isSOTD) {
|
||||||
return res.json({
|
return res.json({
|
||||||
@@ -138,16 +139,17 @@ export function solitaireRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/reset", (req, res) => {
|
router.post("/reset", requireAuth, (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
if (activeSolitaireGames[userId]) {
|
if (activeSolitaireGames[userId]) {
|
||||||
delete activeSolitaireGames[userId];
|
delete activeSolitaireGames[userId];
|
||||||
}
|
}
|
||||||
res.json({ success: true, message: "Game reset." });
|
res.json({ success: true, message: "Game reset." });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/move", async (req, res) => {
|
router.post("/move", requireAuth, async (req, res) => {
|
||||||
const { userId, ...moveData } = req.body;
|
const userId = req.userId;
|
||||||
|
const { ...moveData } = req.body;
|
||||||
const gameState = activeSolitaireGames[userId];
|
const gameState = activeSolitaireGames[userId];
|
||||||
|
|
||||||
if (!gameState) return res.status(404).json({ error: "Game not found." });
|
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) => {
|
router.post("/draw", requireAuth, (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
const gameState = activeSolitaireGames[userId];
|
const gameState = activeSolitaireGames[userId];
|
||||||
|
|
||||||
if (!gameState) return res.status(404).json({ error: "Game not found." });
|
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 });
|
res.json({ success: true, gameState });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/undo", (req, res) => {
|
router.post("/undo", requireAuth, (req, res) => {
|
||||||
const { userId } = req.body;
|
const userId = req.userId;
|
||||||
const gameState = activeSolitaireGames[userId];
|
const gameState = activeSolitaireGames[userId];
|
||||||
|
|
||||||
if (!gameState) return res.status(404).json({ error: "Game not found." });
|
if (!gameState) return res.status(404).json({ error: "Game not found." });
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
formatConnect4BoardForDiscord,
|
formatConnect4BoardForDiscord,
|
||||||
} from "../game/various.js";
|
} from "../game/various.js";
|
||||||
import { eloHandler } from "../game/elo.js";
|
import { eloHandler } from "../game/elo.js";
|
||||||
|
import { verifyToken } from "./middleware/auth.js";
|
||||||
|
|
||||||
// --- Module-level State ---
|
// --- Module-level State ---
|
||||||
let io;
|
let io;
|
||||||
@@ -25,10 +26,23 @@ let io;
|
|||||||
export function initializeSocket(server, client) {
|
export function initializeSocket(server, client) {
|
||||||
io = server;
|
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) => {
|
io.on("connection", (socket) => {
|
||||||
socket.on("user-connected", async (userId) => {
|
socket.on("user-connected", async () => {
|
||||||
if (!userId) return;
|
if (!socket.userId) return;
|
||||||
await refreshQueuesForUser(userId, client);
|
await refreshQueuesForUser(socket.userId, client);
|
||||||
});
|
});
|
||||||
|
|
||||||
registerTicTacToeEvents(socket, client);
|
registerTicTacToeEvents(socket, client);
|
||||||
@@ -39,14 +53,13 @@ export function initializeSocket(server, client) {
|
|||||||
io.emit("blackjack:chat", data);
|
io.emit("blackjack:chat", data);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("tictactoe: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 ({ discordId }) => await refreshQueuesForUser(discordId, client));
|
socket.on("connect4:queue:leave", async () => await refreshQueuesForUser(socket.userId, client));
|
||||||
socket.on("snake:queue:leave", async ({ discordId }) => await refreshQueuesForUser(discordId, client));
|
socket.on("snake:queue:leave", async () => await refreshQueuesForUser(socket.userId, client));
|
||||||
|
|
||||||
// catch tab kills / network drops
|
// catch tab kills / network drops
|
||||||
socket.on("disconnecting", async () => {
|
socket.on("disconnecting", async () => {
|
||||||
const discordId = socket.handshake.auth?.discordId; // or your mapping
|
await refreshQueuesForUser(socket.userId, client);
|
||||||
await refreshQueuesForUser(discordId, client);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
socket.on("disconnect", () => {
|
||||||
@@ -64,24 +77,24 @@ export function getSocketIo() {
|
|||||||
// --- Event Registration ---
|
// --- Event Registration ---
|
||||||
|
|
||||||
function registerTicTacToeEvents(socket, client) {
|
function registerTicTacToeEvents(socket, client) {
|
||||||
socket.on("tictactoeconnection", (e) => refreshQueuesForUser(e.id, client));
|
socket.on("tictactoeconnection", () => refreshQueuesForUser(socket.userId, client));
|
||||||
socket.on("tictactoequeue", (e) => onQueueJoin(client, "tictactoe", e.playerId));
|
socket.on("tictactoequeue", () => onQueueJoin(client, "tictactoe", socket.userId));
|
||||||
socket.on("tictactoeplaying", (e) => onTicTacToeMove(client, e));
|
socket.on("tictactoeplaying", (e) => onTicTacToeMove(client, { ...e, playerId: socket.userId }));
|
||||||
socket.on("tictactoegameOver", (e) => onGameOver(client, "tictactoe", e.playerId, e.winner));
|
socket.on("tictactoegameOver", (e) => onGameOver(client, "tictactoe", socket.userId, e.winner));
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerConnect4Events(socket, client) {
|
function registerConnect4Events(socket, client) {
|
||||||
socket.on("connect4connection", (e) => refreshQueuesForUser(e.id, client));
|
socket.on("connect4connection", () => refreshQueuesForUser(socket.userId, client));
|
||||||
socket.on("connect4queue", (e) => onQueueJoin(client, "connect4", e.playerId));
|
socket.on("connect4queue", () => onQueueJoin(client, "connect4", socket.userId));
|
||||||
socket.on("connect4playing", (e) => onConnect4Move(client, e));
|
socket.on("connect4playing", (e) => onConnect4Move(client, { ...e, playerId: socket.userId }));
|
||||||
socket.on("connect4NoTime", (e) => onGameOver(client, "connect4", e.playerId, e.winner, "(temps écoulé)"));
|
socket.on("connect4NoTime", (e) => onGameOver(client, "connect4", socket.userId, e.winner, "(temps écoulé)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerSnakeEvents(socket, client) {
|
function registerSnakeEvents(socket, client) {
|
||||||
socket.on("snakeconnection", (e) => refreshQueuesForUser(e.id, client));
|
socket.on("snakeconnection", () => refreshQueuesForUser(socket.userId, client));
|
||||||
socket.on("snakequeue", (e) => onQueueJoin(client, "snake", e.playerId));
|
socket.on("snakequeue", () => onQueueJoin(client, "snake", socket.userId));
|
||||||
socket.on("snakegamestate", (e) => onSnakeGameStateUpdate(client, e));
|
socket.on("snakegamestate", (e) => onSnakeGameStateUpdate(client, { ...e, playerId: socket.userId }));
|
||||||
socket.on("snakegameOver", (e) => onGameOver(client, "snake", e.playerId, e.winner));
|
socket.on("snakegameOver", (e) => onGameOver(client, "snake", socket.userId, e.winner));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Core Handlers (Preserving Original Logic) ---
|
// --- Core Handlers (Preserving Original Logic) ---
|
||||||
@@ -95,7 +108,7 @@ async function onQueueJoin(client, gameType, playerId) {
|
|||||||
console.log(`[${title}] Player ${playerId} already in queue, ignoring duplicate join.`);
|
console.log(`[${title}] Player ${playerId} already in queue, ignoring duplicate join.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.values(activeGames).some((g) => g.p1.id === playerId || g.p2.id === playerId)) {
|
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.`);
|
console.log(`[${title}] Player ${playerId} already in active game, ignoring queue join.`);
|
||||||
return;
|
return;
|
||||||
@@ -277,7 +290,7 @@ export async function onGameOver(client, gameType, playerId, winnerId, reason =
|
|||||||
game.p1.id === winnerId ? 1 : 0,
|
game.p1.id === winnerId ? 1 : 0,
|
||||||
game.p2.id === winnerId ? 1 : 0,
|
game.p2.id === winnerId ? 1 : 0,
|
||||||
title.toUpperCase(),
|
title.toUpperCase(),
|
||||||
scores
|
scores,
|
||||||
);
|
);
|
||||||
const winnerName = game.p1.id === winnerId ? game.p1.name : game.p2.name;
|
const winnerName = game.p1.id === winnerId ? game.p1.name : game.p2.name;
|
||||||
resultText = `Victoire de ${winnerName}`;
|
resultText = `Victoire de ${winnerName}`;
|
||||||
@@ -382,7 +395,7 @@ async function createGame(client, gameType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
io.emit(`${gameType}playing`, { allPlayers: Object.values(activeGames) });
|
io.emit(`${gameType}playing`, { allPlayers: Object.values(activeGames) });
|
||||||
|
|
||||||
// For Snake, also emit a specific match notification to the two players
|
// For Snake, also emit a specific match notification to the two players
|
||||||
if (gameType === "snake") {
|
if (gameType === "snake") {
|
||||||
io.emit("snakematch", {
|
io.emit("snakematch", {
|
||||||
@@ -395,7 +408,7 @@ async function createGame(client, gameType) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await emitQueueUpdate(client, gameType);
|
await emitQueueUpdate(client, gameType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ export async function getUsersByElo() {
|
|||||||
include: { elo: true },
|
include: { elo: true },
|
||||||
orderBy: { elo: { elo: "desc" } },
|
orderBy: { elo: { elo: "desc" } },
|
||||||
});
|
});
|
||||||
return users
|
return users.filter((u) => u.elo).map((u) => ({ ...u, elo: u.elo?.elo ?? null }));
|
||||||
.filter((u) => u.elo)
|
|
||||||
.map((u) => ({ ...u, elo: u.elo?.elo ?? null }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toGame(game) {
|
function toGame(game) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import prisma from "../prisma/client.js";
|
import prisma from "../prisma/client.js";
|
||||||
|
|
||||||
function toOffer(offer) {
|
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() {
|
export async function getMarketOffers() {
|
||||||
@@ -40,23 +40,25 @@ export async function getMarketOffersBySkin(skinUuid) {
|
|||||||
buyer: { select: { username: true, globalName: true } },
|
buyer: { select: { username: true, globalName: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return offers.map((offer) => toOffer({
|
return offers.map((offer) =>
|
||||||
...offer,
|
toOffer({
|
||||||
skinName: offer.skin?.displayName,
|
...offer,
|
||||||
skinIcon: offer.skin?.displayIcon,
|
skinName: offer.skin?.displayName,
|
||||||
sellerName: offer.seller?.username,
|
skinIcon: offer.skin?.displayIcon,
|
||||||
sellerGlobalName: offer.seller?.globalName,
|
sellerName: offer.seller?.username,
|
||||||
buyerName: offer.buyer?.username ?? null,
|
sellerGlobalName: offer.seller?.globalName,
|
||||||
buyerGlobalName: offer.buyer?.globalName ?? null,
|
buyerName: offer.buyer?.username ?? null,
|
||||||
}));
|
buyerGlobalName: offer.buyer?.globalName ?? null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function insertMarketOffer(data) {
|
export async function insertMarketOffer(data) {
|
||||||
return prisma.marketOffer.create({
|
return prisma.marketOffer.create({
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
openingAt: new Date(data.openingAt),
|
openingAt: String(data.openingAt),
|
||||||
closingAt: new Date(data.closingAt),
|
closingAt: String(data.closingAt),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,11 @@ export async function deleteSOTD() {
|
|||||||
|
|
||||||
export async function getAllSOTDStats() {
|
export async function getAllSOTDStats() {
|
||||||
const stats = await prisma.sotdStat.findMany({
|
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" }],
|
orderBy: [{ score: "desc" }, { moves: "asc" }, { time: "asc" }],
|
||||||
});
|
});
|
||||||
return stats.map((s) => ({
|
return stats.map((s) => ({
|
||||||
...s,
|
...s,
|
||||||
globalName: s.user?.globalName,
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import prisma from "../prisma/client.js";
|
import prisma from "../prisma/client.js";
|
||||||
|
import { socketEmit } from "../server/socket.js";
|
||||||
|
|
||||||
export async function getUser(id) {
|
export async function getUser(id) {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
@@ -36,6 +37,7 @@ export async function updateUser(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateUserCoins(id, coins) {
|
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 } });
|
return prisma.user.update({ where: { id }, data: { coins } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import { isChampionsSkin } from "./index.js";
|
|||||||
export async function drawCaseContent(caseType = "standard", poolSize = 100) {
|
export async function drawCaseContent(caseType = "standard", poolSize = 100) {
|
||||||
if (caseType === "esport") {
|
if (caseType === "esport") {
|
||||||
// Esport case: return all esport skins
|
// Esport case: return all esport skins
|
||||||
try {
|
try {
|
||||||
const dbSkins = await skinService.getAllAvailableSkins();
|
const dbSkins = await skinService.getAllAvailableSkins();
|
||||||
const esportSkins = [];
|
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);
|
const dbSkin = await skinService.getSkin(s.uuid);
|
||||||
esportSkins.push({
|
esportSkins.push({
|
||||||
...s,
|
...s,
|
||||||
@@ -59,14 +61,14 @@ export async function drawCaseContent(caseType = "standard", poolSize = 100) {
|
|||||||
.filter((s) => dbSkins.find((dbSkin) => dbSkin.uuid === s.uuid))
|
.filter((s) => dbSkins.find((dbSkin) => dbSkin.uuid === s.uuid))
|
||||||
.filter((s) => {
|
.filter((s) => {
|
||||||
if (caseType === "ultra") {
|
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 {
|
} else {
|
||||||
return !s.displayName.toLowerCase().includes("vct");
|
return !s.displayName.toLowerCase().includes("vct");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter((s) => {
|
.filter((s) => {
|
||||||
if (caseType === "ultra") {
|
if (caseType === "ultra") {
|
||||||
return true
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return isChampionsSkin(s.displayName) === false;
|
return isChampionsSkin(s.displayName) === false;
|
||||||
}
|
}
|
||||||
@@ -75,7 +77,8 @@ export async function drawCaseContent(caseType = "standard", poolSize = 100) {
|
|||||||
for (const s of filtered) {
|
for (const s of filtered) {
|
||||||
const dbSkin = await skinService.getSkin(s.uuid);
|
const dbSkin = await skinService.getSkin(s.uuid);
|
||||||
const weight = tierWeights[s.contentTierUuid] ?? 0;
|
const weight = tierWeights[s.contentTierUuid] ?? 0;
|
||||||
if (weight > 0) { // <--- CRITICAL: Remove 0 weight skins
|
if (weight > 0) {
|
||||||
|
// <--- CRITICAL: Remove 0 weight skins
|
||||||
weightedPool.push({
|
weightedPool.push({
|
||||||
...s,
|
...s,
|
||||||
tierColor: dbSkin?.tierColor,
|
tierColor: dbSkin?.tierColor,
|
||||||
@@ -90,7 +93,7 @@ export async function drawCaseContent(caseType = "standard", poolSize = 100) {
|
|||||||
const result = [];
|
const result = [];
|
||||||
|
|
||||||
// 2. Adjust count if the pool is smaller than requested
|
// 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++) {
|
for (let i = 0; i < actualCount; i++) {
|
||||||
let r = Math.random() * totalWeight;
|
let r = Math.random() * totalWeight;
|
||||||
@@ -172,10 +175,19 @@ export async function drawCaseSkin(caseContent) {
|
|||||||
|
|
||||||
export function getSkinUpgradeProbs(skin, skinData) {
|
export function getSkinUpgradeProbs(skin, skinData) {
|
||||||
const successProb =
|
const successProb =
|
||||||
(1 - (((skin.currentChroma + skin.currentLvl + skinData.chromas.length + skinData.levels.length) / 18) * (parseInt(skin.tierRank) / 4)))/1.5;
|
(1 -
|
||||||
const destructionProb = ((skin.currentChroma + skinData.levels.length) / (skinData.chromas.length + skinData.levels.length)) * (parseInt(skin.tierRank) / 5) * 0.075;
|
((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 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 = () => {
|
const calculateNextPrice = () => {
|
||||||
let result = parseFloat(skin.basePrice);
|
let result = parseFloat(skin.basePrice);
|
||||||
result *= 1 + nextLvl / Math.max(skinData.levels.length, 2);
|
result *= 1 + nextLvl / Math.max(skinData.levels.length, 2);
|
||||||
@@ -187,10 +199,18 @@ export function getSkinUpgradeProbs(skin, skinData) {
|
|||||||
return { successProb, destructionProb, upgradePrice };
|
return { successProb, destructionProb, upgradePrice };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDummySkinUpgradeProbs(skinLevel, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, skinMaxPrice) {
|
export function getDummySkinUpgradeProbs(
|
||||||
|
skinLevel,
|
||||||
|
skinChroma,
|
||||||
|
skinTierRank,
|
||||||
|
skinMaxLevels,
|
||||||
|
skinMaxChromas,
|
||||||
|
skinMaxPrice,
|
||||||
|
) {
|
||||||
const successProb =
|
const successProb =
|
||||||
1 - (((skinChroma + skinLevel + (skinMaxChromas + skinMaxLevels)) / 18) * (parseInt(skinTierRank) / 4));
|
1 - ((skinChroma + skinLevel + (skinMaxChromas + skinMaxLevels)) / 18) * (parseInt(skinTierRank) / 4);
|
||||||
const destructionProb = ((skinChroma + skinMaxLevels) / (skinMaxChromas + skinMaxLevels)) * (parseInt(skinTierRank) / 5) * 0.1;
|
const destructionProb =
|
||||||
const upgradePrice = Math.max(Math.floor((parseFloat(skinMaxPrice) * (1 - successProb))), 1);
|
((skinChroma + skinMaxLevels) / (skinMaxChromas + skinMaxLevels)) * (parseInt(skinTierRank) / 5) * 0.1;
|
||||||
|
const upgradePrice = Math.max(Math.floor(parseFloat(skinMaxPrice) * (1 - successProb)), 1);
|
||||||
return { successProb, destructionProb, upgradePrice };
|
return { successProb, destructionProb, upgradePrice };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,10 +432,26 @@ function formatTierText(rank, displayName) {
|
|||||||
|
|
||||||
export function isMeleeSkin(skinName) {
|
export function isMeleeSkin(skinName) {
|
||||||
const name = skinName.toLowerCase();
|
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") ||
|
return !(
|
||||||
name.includes("bucky") || name.includes("judge") || name.includes("bulldog") || name.includes("guardian") ||
|
name.includes("classic") ||
|
||||||
name.includes("vandal") || name.includes("phantom") || name.includes("marshal") || name.includes("outlaw") ||
|
name.includes("shorty") ||
|
||||||
name.includes("operator") || name.includes("ares") || name.includes("odin"));
|
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) {
|
export function isVCTSkin(skinName) {
|
||||||
@@ -444,31 +460,78 @@ export function isVCTSkin(skinName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const VCT_TEAMS = {
|
const VCT_TEAMS = {
|
||||||
"vct-am": [
|
"vct-am": [
|
||||||
/x 100t\)$/g, /x c9\)$/g, /x eg\)$/g, /x fur\)$/g, /x krü\)$/g, /x lev\)$/g, /x loud\)$/g,
|
/x 100t\)$/g,
|
||||||
/x mibr\)$/g, /x sen\)$/g, /x nrg\)$/g, /x g2\)$/g, /x nv\)$/g, /x 2g\)$/g
|
/x c9\)$/g,
|
||||||
],
|
/x eg\)$/g,
|
||||||
"vct-emea": [
|
/x fur\)$/g,
|
||||||
/x bbl\)$/g, /x fnc\)$/g, /x fut\)$/g, /x m8\)$/g, /x gx\)$/g, /x kc\)$/g, /x navi\)$/g,
|
/x krü\)$/g,
|
||||||
/x th\)$/g, /x tl\)$/g, /x vit\)$/g, /x ulf\)$/g, /x pcf\)$/g, /x koi\)$/g, /x apk\)$/g
|
/x lev\)$/g,
|
||||||
],
|
/x loud\)$/g,
|
||||||
"vct-pcf": [
|
/x mibr\)$/g,
|
||||||
/x dfm\)$/g, /x drx\)$/g, /x fs\)$/g, /x gen\)$/g, /x ge\)$/g, /x prx\)$/g, /x rrq\)$/g,
|
/x sen\)$/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
|
/x nrg\)$/g,
|
||||||
],
|
/x g2\)$/g,
|
||||||
"vct-cn": [
|
/x nv\)$/g,
|
||||||
/x ag\)$/g, /x blg\)$/g, /x edg\)$/g, /x fpx\)$/g, /x jdg\)$/g, /x nova\)$/g, /x tec\)$/g,
|
/x 2g\)$/g,
|
||||||
/x te\)$/g, /x tyl\)$/g, /x wol\)$/g, /x xlg\)$/g, /x xlg\)$/g, /x drg\)$/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) {
|
export function getVCTRegion(skinName) {
|
||||||
if (!isVCTSkin(skinName)) return null;
|
if (!isVCTSkin(skinName)) return null;
|
||||||
const name = skinName.toLowerCase().trim();
|
const name = skinName.toLowerCase().trim();
|
||||||
for (const [region, regexes] of Object.entries(VCT_TEAMS)) {
|
for (const [region, regexes] of Object.entries(VCT_TEAMS)) {
|
||||||
if (regexes.some(regex => regex.test(name))) {
|
if (regexes.some((regex) => regex.test(name))) {
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -476,4 +539,4 @@ export function getVCTRegion(skinName) {
|
|||||||
export function isChampionsSkin(skinName) {
|
export function isChampionsSkin(skinName) {
|
||||||
const name = skinName.toLowerCase();
|
const name = skinName.toLowerCase();
|
||||||
return name.includes("champions");
|
return name.includes("champions");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user