mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-03-18 21:40:27 +01:00
feat: micro-transactions
This commit is contained in:
215
package-lock.json
generated
215
package-lock.json
generated
@@ -21,6 +21,7 @@
|
||||
"openai": "^4.104.0",
|
||||
"pokersolver": "^2.1.4",
|
||||
"socket.io": "^4.8.1",
|
||||
"stripe": "^20.3.0",
|
||||
"unique-names-generator": "^4.7.1",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
@@ -36,15 +37,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/builders": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.3.tgz",
|
||||
"integrity": "sha512-p3kf5eV49CJiRTfhtutUCeivSyQ/l2JlKodW1ZquRwwvlOWmG9+6jFShX6x8rUiYhnP6wKI96rgN/SXMy5e5aw==",
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz",
|
||||
"integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/formatters": "^0.6.1",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@discordjs/formatters": "^0.6.2",
|
||||
"@discordjs/util": "^1.2.0",
|
||||
"@sapphire/shapeshift": "^4.0.0",
|
||||
"discord-api-types": "^0.38.16",
|
||||
"discord-api-types": "^0.38.33",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.4",
|
||||
"tslib": "^2.6.3"
|
||||
@@ -66,12 +67,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/formatters": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz",
|
||||
"integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==",
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
|
||||
"integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.1"
|
||||
"discord-api-types": "^0.38.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
@@ -116,10 +117,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/util": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz",
|
||||
"integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
|
||||
"integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -865,29 +869,58 @@
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"version": "1.20.4",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
||||
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"bytes": "~3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.13.0",
|
||||
"raw-body": "2.5.2",
|
||||
"destroy": "~1.2.0",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"on-finished": "~2.4.1",
|
||||
"qs": "~6.14.0",
|
||||
"raw-body": "~2.5.3",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
@@ -1248,9 +1281,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.38.23",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.23.tgz",
|
||||
"integrity": "sha512-C8VjK0yxBUq1dakxGpUXQm4VSC7R+aaD2SIr3paj2a0bP/LRok1AqHiezp30GruK6Ba9FtQAKqYUMJPzsqv7IQ==",
|
||||
"version": "0.38.38",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.38.tgz",
|
||||
"integrity": "sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"scripts/actions/documentation"
|
||||
@@ -1266,19 +1299,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/discord.js": {
|
||||
"version": "14.22.1",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.22.1.tgz",
|
||||
"integrity": "sha512-3k+Kisd/v570Jr68A1kNs7qVhNehDwDJAPe4DZ2Syt+/zobf9zEcuYFvsfIaAOgCa0BiHMfOOKQY4eYINl0z7w==",
|
||||
"version": "14.25.1",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
|
||||
"integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.11.2",
|
||||
"@discordjs/builders": "^1.13.0",
|
||||
"@discordjs/collection": "1.5.3",
|
||||
"@discordjs/formatters": "^0.6.1",
|
||||
"@discordjs/formatters": "^0.6.2",
|
||||
"@discordjs/rest": "^2.6.0",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@discordjs/util": "^1.2.0",
|
||||
"@discordjs/ws": "^1.2.3",
|
||||
"@sapphire/snowflake": "3.5.3",
|
||||
"discord-api-types": "^0.38.16",
|
||||
"discord-api-types": "^0.38.33",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"magic-bytes.js": "^1.10.0",
|
||||
@@ -1729,39 +1762,39 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"version": "4.22.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
||||
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"body-parser": "~1.20.3",
|
||||
"content-disposition": "~0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"cookie": "~0.7.1",
|
||||
"cookie-signature": "~1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.3.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"finalhandler": "~1.3.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.12",
|
||||
"path-to-regexp": "~0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"qs": "~6.14.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.2",
|
||||
"send": "~0.19.0",
|
||||
"serve-static": "~1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"statuses": "~2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
@@ -2601,12 +2634,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
|
||||
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^2.0.0",
|
||||
"jwa": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
@@ -2651,9 +2684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
@@ -3290,12 +3323,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@@ -3314,20 +3347,49 @@
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
||||
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
"bytes": "~3.1.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body/node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body/node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
@@ -3910,6 +3972,23 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stripe": {
|
||||
"version": "20.3.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-20.3.0.tgz",
|
||||
"integrity": "sha512-DYzcmV1MfYhycr1GwjCjeQVYk9Gu8dpxyTlu7qeDCsuguug7oUTxPsUQuZeSf/OPzK7pofqobvOKVqAwlpgf/Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=16"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"openai": "^4.104.0",
|
||||
"pokersolver": "^2.1.4",
|
||||
"socket.io": "^4.8.1",
|
||||
"stripe": "^20.3.0",
|
||||
"unique-names-generator": "^4.7.1",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { sleep } from "openai/core";
|
||||
import { AttachmentBuilder } from "discord.js";
|
||||
import {
|
||||
buildAiMessages,
|
||||
buildParticipantsMap,
|
||||
@@ -52,10 +53,10 @@ export async function handleMessageCreate(message, client, io) {
|
||||
// --- Main Guild Features (Points & Slowmode) ---
|
||||
if (message.guildId === process.env.GUILD_ID) {
|
||||
// Award points for activity
|
||||
const pointsAwarded = channelPointsHandler(message);
|
||||
if (pointsAwarded) {
|
||||
io.emit("data-updated", { table: "users", action: "update" });
|
||||
}
|
||||
// const pointsAwarded = channelPointsHandler(message);
|
||||
// if (pointsAwarded) {
|
||||
// io.emit("data-updated", { table: "users", action: "update" });
|
||||
// }
|
||||
|
||||
// Enforce active slowmodes
|
||||
const wasSlowmoded = await slowmodesHandler(message, activeSlowmodes);
|
||||
@@ -245,7 +246,10 @@ async function handleAdminCommands(message) {
|
||||
try {
|
||||
const stmt = flopoDB.prepare(sqlCommand);
|
||||
const result = sqlCommand.trim().toUpperCase().startsWith("SELECT") ? stmt.all() : stmt.run();
|
||||
message.reply("```json\n" + JSON.stringify(result, null, 2).substring(0, 1900) + "\n```");
|
||||
const jsonString = JSON.stringify(result, null, 2);
|
||||
const buffer = Buffer.from(jsonString, "utf-8");
|
||||
const attachment = new AttachmentBuilder(buffer, { name: "sql-result.json" });
|
||||
message.reply({ content: "SQL query executed successfully:", files: [attachment] });
|
||||
} catch (e) {
|
||||
message.reply(`SQL Error: ${e.message}`);
|
||||
}
|
||||
|
||||
@@ -269,6 +269,49 @@ flopoDB.exec(`
|
||||
score
|
||||
INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transactions
|
||||
(
|
||||
id
|
||||
TEXT
|
||||
PRIMARY
|
||||
KEY,
|
||||
session_id
|
||||
TEXT
|
||||
UNIQUE
|
||||
NOT
|
||||
NULL,
|
||||
user_id
|
||||
TEXT
|
||||
REFERENCES
|
||||
users
|
||||
NOT
|
||||
NULL,
|
||||
coins_amount
|
||||
INTEGER
|
||||
NOT
|
||||
NULL,
|
||||
amount_cents
|
||||
INTEGER
|
||||
NOT
|
||||
NULL,
|
||||
currency
|
||||
TEXT
|
||||
DEFAULT
|
||||
'eur',
|
||||
customer_email
|
||||
TEXT,
|
||||
customer_name
|
||||
TEXT,
|
||||
payment_status
|
||||
TEXT
|
||||
NOT
|
||||
NULL,
|
||||
created_at
|
||||
DATETIME
|
||||
DEFAULT
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
/* -----------------------------------------------------
|
||||
@@ -566,6 +609,52 @@ export const stmtSOTDStats = flopoDB.prepare(`
|
||||
`);
|
||||
stmtSOTDStats.run();
|
||||
|
||||
export const stmtTransactions = flopoDB.prepare(`
|
||||
CREATE TABLE IF NOT EXISTS transactions
|
||||
(
|
||||
id
|
||||
TEXT
|
||||
PRIMARY
|
||||
KEY,
|
||||
session_id
|
||||
TEXT
|
||||
UNIQUE
|
||||
NOT
|
||||
NULL,
|
||||
user_id
|
||||
TEXT
|
||||
REFERENCES
|
||||
users
|
||||
NOT
|
||||
NULL,
|
||||
coins_amount
|
||||
INTEGER
|
||||
NOT
|
||||
NULL,
|
||||
amount_cents
|
||||
INTEGER
|
||||
NOT
|
||||
NULL,
|
||||
currency
|
||||
TEXT
|
||||
DEFAULT
|
||||
'eur',
|
||||
customer_email
|
||||
TEXT,
|
||||
customer_name
|
||||
TEXT,
|
||||
payment_status
|
||||
TEXT
|
||||
NOT
|
||||
NULL,
|
||||
created_at
|
||||
DATETIME
|
||||
DEFAULT
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
stmtTransactions.run();
|
||||
|
||||
/* -------------------------
|
||||
USER statements
|
||||
----------------------------*/
|
||||
@@ -861,3 +950,23 @@ export async function pruneOldLogs() {
|
||||
|
||||
transaction();
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
TRANSACTION statements
|
||||
----------------------------*/
|
||||
export const insertTransaction = flopoDB.prepare(
|
||||
`INSERT INTO transactions (id, session_id, user_id, coins_amount, amount_cents, currency, customer_email, customer_name, payment_status)
|
||||
VALUES (@id, @session_id, @user_id, @coins_amount, @amount_cents, @currency, @customer_email, @customer_name, @payment_status)`,
|
||||
);
|
||||
|
||||
export const getTransactionBySessionId = flopoDB.prepare(
|
||||
`SELECT * FROM transactions WHERE session_id = ?`,
|
||||
);
|
||||
|
||||
export const getAllTransactions = flopoDB.prepare(
|
||||
`SELECT * FROM transactions ORDER BY created_at DESC`,
|
||||
);
|
||||
|
||||
export const getUserTransactions = flopoDB.prepare(
|
||||
`SELECT * FROM transactions WHERE user_id = ? ORDER BY created_at DESC`,
|
||||
);
|
||||
|
||||
@@ -37,6 +37,9 @@ app.post("/interactions", verifyKeyMiddleware(process.env.PUBLIC_KEY), async (re
|
||||
await handleInteraction(req, res, client);
|
||||
});
|
||||
|
||||
// Stripe webhook endpoint needs raw body for signature verification
|
||||
app.use("/api/buy-coins", express.raw({ type: "application/json" }));
|
||||
|
||||
// JSON Body Parser Middleware
|
||||
app.use(express.json());
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import express from "express";
|
||||
import { sleep } from "openai/core";
|
||||
import Stripe from "stripe";
|
||||
|
||||
// --- Database Imports ---
|
||||
import {
|
||||
@@ -21,6 +22,10 @@ import {
|
||||
queryDailyReward,
|
||||
updateSkin,
|
||||
updateUserCoins,
|
||||
insertTransaction,
|
||||
getTransactionBySessionId,
|
||||
getAllTransactions,
|
||||
getUserTransactions,
|
||||
} from "../../database/index.js";
|
||||
|
||||
// --- Game State Imports ---
|
||||
@@ -1277,24 +1282,174 @@ export function apiRoutes(client, io) {
|
||||
}
|
||||
});
|
||||
|
||||
// --- Admin Routes ---
|
||||
// Fixed coin offers - server-side source of truth
|
||||
const COIN_OFFERS = [
|
||||
{ id: "offer_5000", coins: 5000, amount_cents: 99, label: "5 000 FlopoCoins" },
|
||||
{ id: "offer_20000", coins: 20000, amount_cents: 299, label: "20 000 FlopoCoins" },
|
||||
{ id: "offer_40000", coins: 40000, amount_cents: 499, label: "40 000 FlopoCoins" },
|
||||
{ id: "offer_100000", coins: 100000, amount_cents: 999, label: "100 000 FlopoCoins" },
|
||||
];
|
||||
|
||||
router.post("/buy-coins", (req, res) => {
|
||||
const { commandUserId, coins } = req.body;
|
||||
const user = getUser.get(commandUserId);
|
||||
if (!user) return res.status(404).json({ error: "User not found" });
|
||||
router.get("/coin-offers", (req, res) => {
|
||||
res.json({ offers: COIN_OFFERS });
|
||||
});
|
||||
|
||||
const newCoins = user.coins + coins;
|
||||
updateUserCoins.run({ id: commandUserId, coins: newCoins });
|
||||
insertLog.run({
|
||||
id: `${commandUserId}-buycoins-${Date.now()}`,
|
||||
user_id: commandUserId,
|
||||
action: "BUY_COINS_ADMIN",
|
||||
coins_amount: coins,
|
||||
user_new_amount: newCoins,
|
||||
});
|
||||
router.post("/create-checkout-session", async (req, res) => {
|
||||
const { userId, offerId } = req.body;
|
||||
|
||||
res.status(200).json({ message: `Added ${coins} coins.` });
|
||||
if (!userId || !offerId) {
|
||||
return res.status(400).json({ error: "Missing required fields: userId, offerId" });
|
||||
}
|
||||
|
||||
const offer = COIN_OFFERS.find((o) => o.id === offerId);
|
||||
if (!offer) {
|
||||
return res.status(400).json({ error: "Invalid offer" });
|
||||
}
|
||||
|
||||
const user = getUser.get(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
try {
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
||||
const FLAPI_URL = process.env.DEV_SITE === "true" ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL;
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: 'eur',
|
||||
product_data: {
|
||||
name: offer.label,
|
||||
description: `Achat de ${offer.label} pour FlopoBot`,
|
||||
},
|
||||
unit_amount: offer.amount_cents,
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
mode: 'payment',
|
||||
success_url: `${FLAPI_URL}/payment-success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${FLAPI_URL}/dashboard`,
|
||||
metadata: {
|
||||
userId: userId,
|
||||
coins: offer.coins.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
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 });
|
||||
} catch (error) {
|
||||
console.error("Error creating checkout session:", error);
|
||||
res.status(500).json({ error: "Failed to create checkout session" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/buy-coins", async (req, res) => {
|
||||
const sig = req.headers['stripe-signature'];
|
||||
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
||||
|
||||
if (!endpointSecret) {
|
||||
console.error("STRIPE_WEBHOOK_SECRET not configured");
|
||||
return res.status(500).json({ error: "Webhook not configured" });
|
||||
}
|
||||
|
||||
let event;
|
||||
|
||||
try {
|
||||
// Verify webhook signature - requires raw body
|
||||
// Note: You need to configure Express to preserve raw body for this route
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
||||
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
|
||||
} catch (err) {
|
||||
console.error(`Webhook signature verification failed: ${err.message}`);
|
||||
return res.status(400).json({ error: `Webhook Error: ${err.message}` });
|
||||
}
|
||||
|
||||
// Handle the event
|
||||
if (event.type === 'checkout.session.completed') {
|
||||
const session = event.data.object;
|
||||
|
||||
// Extract metadata from the checkout session
|
||||
const commandUserId = session.metadata?.userId;
|
||||
const expectedCoins = parseInt(session.metadata?.coins);
|
||||
const amountPaid = session.amount_total; // in cents
|
||||
const currency = session.currency;
|
||||
const customerEmail = session.customer_details?.email;
|
||||
const customerName = session.customer_details?.name;
|
||||
|
||||
// Validate metadata exists
|
||||
if (!commandUserId || !expectedCoins) {
|
||||
console.error("Missing userId or coins in session metadata");
|
||||
return res.status(400).json({ error: "Invalid session metadata" });
|
||||
}
|
||||
|
||||
// Verify payment was successful
|
||||
if (session.payment_status !== 'paid') {
|
||||
console.error(`Payment not completed for session ${session.id}`);
|
||||
return res.status(400).json({ error: "Payment not completed" });
|
||||
}
|
||||
|
||||
// Check for duplicate processing (idempotency)
|
||||
const existingTransaction = getTransactionBySessionId.get(session.id);
|
||||
if (existingTransaction) {
|
||||
console.log(`Payment already processed: ${session.id}`);
|
||||
return res.status(200).json({ message: "Already processed" });
|
||||
}
|
||||
|
||||
// Get user
|
||||
const user = getUser.get(commandUserId);
|
||||
if (!user) {
|
||||
console.error(`User not found: ${commandUserId}`);
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
// Update coins
|
||||
const newCoins = user.coins + expectedCoins;
|
||||
updateUserCoins.run({ id: commandUserId, coins: newCoins });
|
||||
|
||||
// Insert transaction record
|
||||
const transactionId = `${commandUserId}-transaction-${Date.now()}`;
|
||||
insertTransaction.run({
|
||||
id: transactionId,
|
||||
session_id: session.id,
|
||||
user_id: commandUserId,
|
||||
coins_amount: expectedCoins,
|
||||
amount_cents: amountPaid,
|
||||
currency: currency,
|
||||
customer_email: customerEmail,
|
||||
customer_name: customerName,
|
||||
payment_status: session.payment_status,
|
||||
});
|
||||
|
||||
// Insert log entry
|
||||
insertLog.run({
|
||||
id: `${commandUserId}-buycoins-${Date.now()}`,
|
||||
user_id: commandUserId,
|
||||
action: "BUY_COINS",
|
||||
target_user_id: null,
|
||||
coins_amount: expectedCoins,
|
||||
user_new_amount: newCoins,
|
||||
});
|
||||
|
||||
console.log(`Payment processed: ${commandUserId} purchased ${expectedCoins} coins for ${amountPaid/100} ${currency}`);
|
||||
|
||||
// Notify user via Discord if possible
|
||||
try {
|
||||
const discordUser = await client.users.fetch(commandUserId);
|
||||
await discordUser.send(`✅ Votre achat de ${expectedCoins} FlopoCoins a été confirmé ! Merci pour votre soutien !`);
|
||||
} catch (e) {
|
||||
console.log(`Could not DM user ${commandUserId}:`, e.message);
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: `Added ${expectedCoins} coins.` });
|
||||
}
|
||||
|
||||
// Return 200 for unhandled event types (Stripe requires this)
|
||||
res.status(200).json({ received: true });
|
||||
});
|
||||
|
||||
return router;
|
||||
|
||||
Reference in New Issue
Block a user