Version Estable

This commit is contained in:
2025-09-17 13:33:10 -05:00
commit bf6a7e3024
39 changed files with 2537 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules
# Keep environment variables out of version control
.env
/src/generated/prisma

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/amayo.iml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/amayo.iml" filepath="$PROJECT_DIR$/.idea/amayo.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
save-exact=true

989
package-lock.json generated Normal file
View File

@@ -0,0 +1,989 @@
{
"name": "amayo",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "amayo",
"version": "0.0.1",
"license": "ISC",
"dependencies": {
"@prisma/client": "6.16.2",
"discord-api-types": "0.38.24",
"discord.js": "14.22.1",
"prisma": "6.16.2",
"redis": "5.8.2"
},
"devDependencies": {
"@types/node": "24.3.1",
"ts-node": "10.9.2",
"typescript": "5.9.2"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"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==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/formatters": "^0.6.1",
"@discordjs/util": "^1.1.1",
"@sapphire/shapeshift": "^4.0.0",
"discord-api-types": "^0.38.16",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.4",
"tslib": "^2.6.3"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/collection": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=16.11.0"
}
},
"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==",
"license": "Apache-2.0",
"dependencies": {
"discord-api-types": "^0.38.1"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/rest": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz",
"integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.1",
"@discordjs/util": "^1.1.1",
"@sapphire/async-queue": "^1.5.3",
"@sapphire/snowflake": "^3.5.3",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "^0.38.16",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.21.3"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"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==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/ws": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.0",
"@discordjs/rest": "^2.5.1",
"@discordjs/util": "^1.1.0",
"@sapphire/async-queue": "^1.5.2",
"@types/ws": "^8.5.10",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "^0.38.1",
"tslib": "^2.6.2",
"ws": "^8.17.0"
},
"engines": {
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@prisma/client": {
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.2.tgz",
"integrity": "sha512-E00PxBcalMfYO/TWnXobBVUai6eW/g5OsifWQsQDzJYm7yaY+IRLo7ZLsaefi0QkTpxfuhFcQ/w180i6kX3iJw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"peerDependencies": {
"prisma": "*",
"typescript": ">=5.1.0"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/@prisma/config": {
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.2.tgz",
"integrity": "sha512-mKXSUrcqXj0LXWPmJsK2s3p9PN+aoAbyMx7m5E1v1FufofR1ZpPoIArjjzOIm+bJRLLvYftoNYLx1tbHgF9/yg==",
"license": "Apache-2.0",
"dependencies": {
"c12": "3.1.0",
"deepmerge-ts": "7.1.5",
"effect": "3.16.12",
"empathic": "2.0.0"
}
},
"node_modules/@prisma/debug": {
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.2.tgz",
"integrity": "sha512-bo4/gA/HVV6u8YK2uY6glhNsJ7r+k/i5iQ9ny/3q5bt9ijCj7WMPUwfTKPvtEgLP+/r26Z686ly11hhcLiQ8zA==",
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.2.tgz",
"integrity": "sha512-7yf3AjfPUgsg/l7JSu1iEhsmZZ/YE00yURPjTikqm2z4btM0bCl2coFtTGfeSOWbQMmq45Jab+53yGUIAT1sjA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.16.2",
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"@prisma/fetch-engine": "6.16.2",
"@prisma/get-platform": "6.16.2"
}
},
"node_modules/@prisma/engines-version": {
"version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43.tgz",
"integrity": "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==",
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.2.tgz",
"integrity": "sha512-wPnZ8DMRqpgzye758ZvfAMiNJRuYpz+rhgEBZi60ZqDIgOU2694oJxiuu3GKFeYeR/hXxso4/2oBC243t/whxQ==",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.16.2",
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"@prisma/get-platform": "6.16.2"
}
},
"node_modules/@prisma/get-platform": {
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.2.tgz",
"integrity": "sha512-U/P36Uke5wS7r1+omtAgJpEB94tlT4SdlgaeTc6HVTTT93pXj7zZ+B/cZnmnvjcNPfWddgoDx8RLjmQwqGDYyA==",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.16.2"
}
},
"node_modules/@redis/bloom": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.8.2.tgz",
"integrity": "sha512-855DR0ChetZLarblio5eM0yLwxA9Dqq50t8StXKp5bAtLT0G+rZ+eRzzqxl37sPqQKjUudSYypz55o6nNhbz0A==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.8.2"
}
},
"node_modules/@redis/client": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-5.8.2.tgz",
"integrity": "sha512-WtMScno3+eBpTac1Uav2zugXEoXqaU23YznwvFgkPwBQVwEHTDgOG7uEAObtZ/Nyn8SmAMbqkEubJaMOvnqdsQ==",
"license": "MIT",
"dependencies": {
"cluster-key-slot": "1.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@redis/json": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-5.8.2.tgz",
"integrity": "sha512-uxpVfas3I0LccBX9rIfDgJ0dBrUa3+0Gc8sEwmQQH0vHi7C1Rx1Qn8Nv1QWz5bohoeIXMICFZRcyDONvum2l/w==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.8.2"
}
},
"node_modules/@redis/search": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-5.8.2.tgz",
"integrity": "sha512-cNv7HlgayavCBXqPXgaS97DRPVWFznuzsAmmuemi2TMCx5scwLiP50TeZvUS06h/MG96YNPe6A0Zt57yayfxwA==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.8.2"
}
},
"node_modules/@redis/time-series": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.8.2.tgz",
"integrity": "sha512-g2NlHM07fK8H4k+613NBsk3y70R2JIM2dPMSkhIjl2Z17SYvaYKdusz85d7VYOrZBWtDrHV/WD2E3vGu+zni8A==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.8.2"
}
},
"node_modules/@sapphire/async-queue": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
"license": "MIT",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@sapphire/shapeshift": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=v16"
}
},
"node_modules/@sapphire/snowflake": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
"license": "MIT",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
"license": "MIT"
},
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz",
"integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.10.0"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@vladfrangu/async_event_emitter": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz",
"integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==",
"license": "MIT",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"license": "MIT"
},
"node_modules/c12": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
"integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.3",
"confbox": "^0.2.2",
"defu": "^6.1.4",
"dotenv": "^16.6.1",
"exsolve": "^1.0.7",
"giget": "^2.0.0",
"jiti": "^2.4.2",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
"perfect-debounce": "^1.0.0",
"pkg-types": "^2.2.0",
"rc9": "^2.1.2"
},
"peerDependencies": {
"magicast": "^0.3.5"
},
"peerDependenciesMeta": {
"magicast": {
"optional": true
}
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/citty": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
"license": "MIT",
"dependencies": {
"consola": "^3.2.3"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/confbox": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
"license": "MIT"
},
"node_modules/consola": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
"license": "MIT",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/deepmerge-ts": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
"integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"license": "MIT"
},
"node_modules/destr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
"license": "MIT"
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/discord-api-types": {
"version": "0.38.24",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.24.tgz",
"integrity": "sha512-P7/DkcFIiIoaBogStnhhcGRX7KR+gIFp0SpmwsZUIM0bgDkYMEUx+8l+t3quYc/KSgg92wvE9w/4mabO57EMug==",
"license": "MIT",
"workspaces": [
"scripts/actions/documentation"
]
},
"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==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/builders": "^1.11.2",
"@discordjs/collection": "1.5.3",
"@discordjs/formatters": "^0.6.1",
"@discordjs/rest": "^2.6.0",
"@discordjs/util": "^1.1.1",
"@discordjs/ws": "^1.2.3",
"@sapphire/snowflake": "3.5.3",
"discord-api-types": "^0.38.16",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.21.3"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/effect": {
"version": "3.16.12",
"resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz",
"integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"fast-check": "^3.23.1"
}
},
"node_modules/empathic": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/exsolve": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
"integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
"license": "MIT"
},
"node_modules/fast-check": {
"version": "3.23.2",
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
"integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
],
"license": "MIT",
"dependencies": {
"pure-rand": "^6.1.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/giget": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
"license": "MIT",
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.4.0",
"defu": "^6.1.4",
"node-fetch-native": "^1.6.6",
"nypm": "^0.6.0",
"pathe": "^2.0.3"
},
"bin": {
"giget": "dist/cli.mjs"
}
},
"node_modules/jiti": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.snakecase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
"license": "MIT"
},
"node_modules/magic-bytes.js": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz",
"integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==",
"license": "MIT"
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true,
"license": "ISC"
},
"node_modules/node-fetch-native": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
"license": "MIT"
},
"node_modules/nypm": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
"integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
"license": "MIT",
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.4.2",
"pathe": "^2.0.3",
"pkg-types": "^2.3.0",
"tinyexec": "^1.0.1"
},
"bin": {
"nypm": "dist/cli.mjs"
},
"engines": {
"node": "^14.16.0 || >=16.10.0"
}
},
"node_modules/ohash": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"license": "MIT"
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"license": "MIT"
},
"node_modules/pkg-types": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
"license": "MIT",
"dependencies": {
"confbox": "^0.2.2",
"exsolve": "^1.0.7",
"pathe": "^2.0.3"
}
},
"node_modules/prisma": {
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.2.tgz",
"integrity": "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/config": "6.16.2",
"@prisma/engines": "6.16.2"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": ">=18.18"
},
"peerDependencies": {
"typescript": ">=5.1.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/pure-rand": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
],
"license": "MIT"
},
"node_modules/rc9": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
"license": "MIT",
"dependencies": {
"defu": "^6.1.4",
"destr": "^2.0.3"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/redis": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-5.8.2.tgz",
"integrity": "sha512-31vunZj07++Y1vcFGcnNWEf5jPoTkGARgfWI4+Tk55vdwHxhAvug8VEtW7Cx+/h47NuJTEg/JL77zAwC6E0OeA==",
"license": "MIT",
"dependencies": {
"@redis/bloom": "5.8.2",
"@redis/client": "5.8.2",
"@redis/json": "5.8.2",
"@redis/search": "5.8.2",
"@redis/time-series": "5.8.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/tinyexec": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
"license": "MIT"
},
"node_modules/ts-mixer": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
"license": "MIT"
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici": {
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/undici-types": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"license": "MIT"
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"license": "MIT"
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
}
}
}

25
package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "amayo",
"version": "0.0.1",
"description": "",
"main": "src/main.ts",
"scripts": {
"dev": "npx tsx watch src/main.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@prisma/client": "6.16.2",
"discord-api-types": "0.38.24",
"discord.js": "14.22.1",
"prisma": "6.16.2",
"redis": "5.8.2"
},
"devDependencies": {
"@types/node": "24.3.1",
"ts-node": "10.9.2",
"typescript": "5.9.2"
}
}

BIN
prisma/dev.db Normal file

Binary file not shown.

View File

@@ -0,0 +1,62 @@
-- CreateTable
CREATE TABLE "Guild" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY
);
-- CreateTable
CREATE TABLE "PartnershipStats" (
"totalPoints" INTEGER NOT NULL DEFAULT 0,
"weeklyPoints" INTEGER NOT NULL DEFAULT 0,
"monthlyPoints" INTEGER NOT NULL DEFAULT 0,
"lastWeeklyReset" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastMonthlyReset" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
PRIMARY KEY ("userId", "guildId"),
CONSTRAINT "PartnershipStats_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "PartnershipStats_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Alliance" (
"id" TEXT NOT NULL PRIMARY KEY,
"channelId" TEXT NOT NULL,
"messageId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"guildId" TEXT NOT NULL,
"creatorId" TEXT NOT NULL,
CONSTRAINT "Alliance_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Alliance_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "EmbedConfig" (
"id" TEXT NOT NULL PRIMARY KEY,
"color" TEXT,
"title" TEXT,
"url" TEXT,
"authorName" TEXT,
"authorIconURL" TEXT,
"authorURL" TEXT,
"description" TEXT,
"thumbnailURL" TEXT,
"imageURL" TEXT,
"footerText" TEXT,
"footerIconURL" TEXT,
"fields" TEXT DEFAULT '[]',
"guildId" TEXT NOT NULL,
CONSTRAINT "EmbedConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Alliance_messageId_key" ON "Alliance"("messageId");
-- CreateIndex
CREATE UNIQUE INDEX "EmbedConfig_guildId_key" ON "EmbedConfig"("guildId");

View File

@@ -0,0 +1,33 @@
/*
Warnings:
- Added the required column `name` to the `EmbedConfig` table without a default value. This is not possible if the table is not empty.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_EmbedConfig" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"color" TEXT,
"title" TEXT,
"url" TEXT,
"authorName" TEXT,
"authorIconURL" TEXT,
"authorURL" TEXT,
"description" TEXT,
"thumbnailURL" TEXT,
"imageURL" TEXT,
"footerText" TEXT,
"footerIconURL" TEXT,
"fields" TEXT DEFAULT '[]',
"guildId" TEXT NOT NULL,
CONSTRAINT "EmbedConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_EmbedConfig" ("authorIconURL", "authorName", "authorURL", "color", "description", "fields", "footerIconURL", "footerText", "guildId", "id", "imageURL", "thumbnailURL", "title", "url") SELECT "authorIconURL", "authorName", "authorURL", "color", "description", "fields", "footerIconURL", "footerText", "guildId", "id", "imageURL", "thumbnailURL", "title", "url" FROM "EmbedConfig";
DROP TABLE "EmbedConfig";
ALTER TABLE "new_EmbedConfig" RENAME TO "EmbedConfig";
CREATE UNIQUE INDEX "EmbedConfig_guildId_name_key" ON "EmbedConfig"("guildId", "name");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -0,0 +1,13 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Guild" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"prefix" TEXT NOT NULL DEFAULT '!'
);
INSERT INTO "new_Guild" ("id", "name") SELECT "id", "name" FROM "Guild";
DROP TABLE "Guild";
ALTER TABLE "new_Guild" RENAME TO "Guild";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View 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"

133
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,133 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
/*
* -----------------------------------------------------------------------------
* Modelo para el Servidor (Guild)
* -----------------------------------------------------------------------------
*/
model Guild {
id String @id
name String
prefix String @default("!")
// Relaciones
alliances Alliance[]
partnerStats PartnershipStats[]
// ✅ CAMBIO: Ahora un Guild puede tener MÚLTIPLES configuraciones de embed.
embedConfigs EmbedConfig[]
}
/*
* -----------------------------------------------------------------------------
* Modelo para el Usuario
* -----------------------------------------------------------------------------
* Representa a un usuario de Discord de manera global.
*/
model User {
id String @id
// Relaciones
partnerStats PartnershipStats[]
createdAlliances Alliance[]
}
/*
* -----------------------------------------------------------------------------
* Modelo para las Estadísticas de Alianza (Leaderboard)
* -----------------------------------------------------------------------------
* Almacena los puntos de un usuario EN UN SERVIDOR específico.
* Se gana 1 punto por mensaje en los canales registrados.
*/
model PartnershipStats {
// Puntos acumulados totales.
totalPoints Int @default(0)
// Puntos para la tabla de clasificación semanal.
weeklyPoints Int @default(0)
// Puntos para la tabla de clasificación mensual.
monthlyPoints Int @default(0)
// Fecha del último reinicio para controlar los contadores.
lastWeeklyReset DateTime @default(now())
lastMonthlyReset DateTime @default(now())
// --- Relaciones y Clave Primaria ---
user User @relation(fields: [userId], references: [id])
userId String
guild Guild @relation(fields: [guildId], references: [id])
guildId String
// Un usuario solo puede tener un registro de estadísticas por servidor.
@@id([userId, guildId])
}
/*
* -----------------------------------------------------------------------------
* Modelo para la Alianza (El mensaje publicado)
* -----------------------------------------------------------------------------
* Guarda la referencia al mensaje de alianza, pero no su contenido.
* El contenido se construye dinámicamente usando EmbedConfig y PartnershipStats.
*/
model Alliance {
id String @id @default(cuid())
channelId String
messageId String @unique
createdAt DateTime @default(now())
// --- Relaciones ---
guild Guild @relation(fields: [guildId], references: [id])
guildId String
creator User @relation(fields: [creatorId], references: [id])
creatorId String
}
/*
* -----------------------------------------------------------------------------
* Modelo para la Configuración del Embed
* -----------------------------------------------------------------------------
*/
model EmbedConfig {
id String @id @default(cuid())
// ✅ NUEVO: Un nombre único para identificar este embed dentro del servidor.
// Ejemplos: "alianza", "bienvenida", "reglas"
name String
// Campos del Embed (título, descripción, color, etc.)
color String?
title String?
url String?
authorName String?
authorIconURL String?
authorURL String?
description String?
thumbnailURL String?
imageURL String?
footerText String?
footerIconURL String?
fields String? @default("[]")
// --- Relación ---
guild Guild @relation(fields: [guildId], references: [id])
// ✅ CAMBIO: Quitamos '@unique' para permitir que un guildId aparezca múltiples veces.
guildId String
// ✅ NUEVO: Asegura que el 'name' sea único por cada servidor.
// No puedes tener dos embeds llamados "alianza" en el mismo servidor.
@@unique([guildId, name])
}

View File

@@ -0,0 +1,292 @@
import { CommandMessage } from "../../../core/types/commands";
// @ts-ignore
import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, TextChannel, ChannelType } from "discord.js";
//@ts-ignore
import { ButtonStyle, ComponentType } from "discord.js";
import { replaceVars } from "../../../core/lib/vars";
export const command: CommandMessage = {
name: "embedcreate",
type: "message",
aliases: ["crearembed", "newembed"],
cooldown: 20,
// @ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embedName: string | null = args[0] ?? null;
if (!embedName) {
return message.reply(
"Debes proporcionar un nombre para el embed. Uso: `!embedcreate <nombre>`"
);
}
const nameIsValid = await client.prisma.embedConfig.findFirst({ where: {
//@ts-ignore
guildId: message.guild.id,
name: embedName
}})
if(nameIsValid) return message.reply("❌ Nombre del embed ya fue tomado!")
// 📌 Estado independiente
let embedState: {
title?: string;
description?: string;
color?: number;
footer?: string;
} = {
title: `Editor de Embed: ${embedName}`,
description:
"Usa los botones de abajo para configurar este embed.\n\n_Ejemplo de variable: `{user.name}`_",
color: 0x5865f2,
footer: "Haz clic en Guardar cuando termines.",
};
// 📌 Función para construir un embed a partir del estado
const renderPreview = async () => {
const preview = new EmbedBuilder()
.setColor(embedState.color ?? 0x5865f2);
if (embedState.title)
preview.setTitle(
//@ts-ignore
await replaceVars(embedState.title, message.member)
);
if (embedState.description)
preview.setDescription(
//@ts-ignore
await replaceVars(embedState.description, message.member)
);
if (embedState.footer)
preview.setFooter({
//@ts-ignore
text: await replaceVars(embedState.footer, message.member),
});
return preview;
};
// 📌 Botones
const generateButtonRows = (disabled = false) => {
const primaryRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("edit_title")
.setLabel("Título")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("edit_description")
.setLabel("Descripción")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("edit_color")
.setLabel("Color")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled)
);
const secondaryRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("edit_footer")
.setLabel("Footer")
.setStyle(ButtonStyle.Secondary)
.setDisabled(disabled)
);
const controlRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("save_embed")
.setLabel("Guardar")
.setStyle(ButtonStyle.Success)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("cancel_embed")
.setLabel("Cancelar")
.setStyle(ButtonStyle.Danger)
.setDisabled(disabled)
);
return [primaryRow, secondaryRow, controlRow];
};
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
const editorMessage = await channel.send({
embeds: [await renderPreview()],
components: generateButtonRows(),
});
const collector = editorMessage.createMessageComponentCollector({
componentType: ComponentType.Button,
time: 300000,
});
collector.on("collect", async (i) => {
if (i.user.id !== message.author.id) {
await i.reply({
content: "No puedes usar este menú.",
ephemeral: true,
});
return;
}
await i.deferUpdate();
await editorMessage.edit({ components: generateButtonRows(true) });
// Guardar
if (i.customId === "save_embed") {
try {
const dataForDb = {
title: embedState.title,
description: embedState.description,
color: embedState.color ? `#${embedState.color.toString(16).padStart(6, '0')}` : null,
footerText: embedState.footer,
};
await client.prisma.embedConfig.upsert({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
update: dataForDb,
create: {
name: embedName,
...dataForDb,
// ✅ ESTA ES LA SOLUCIÓN:
// Le decimos a Prisma que se conecte al Guild o lo cree si no existe.
guild: {
connectOrCreate: {
where: { id: message.guildId! },
create: {
id: message.guildId!,
name: message.guild!.name, // Asegura que el nombre del servidor se guarde
},
},
},
},
});
const saved = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle(`✅ Guardado: ${embedName}`)
.setDescription("La configuración se guardó en la base de datos.");
await editorMessage.edit({
embeds: [saved],
components: [],
});
} catch (e) {
const errorEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("❌ Error al Guardar")
.setDescription("No se pudo guardar en la base de datos. Revisa la consola.");
await editorMessage.edit({
embeds: [errorEmbed],
components: [],
});
console.error("Error de Prisma al guardar el embed:", e);
}
collector.stop();
return;
}
// Cancelar
if (i.customId === "cancel_embed") {
await editorMessage.delete();
collector.stop();
return;
}
// Edición
let promptContent = "";
let fieldToEdit: "title" | "description" | "color" | "footer" | null =
null;
switch (i.customId) {
case "edit_title":
promptContent =
"Escribe el nuevo **título** (puedes usar variables como `{user.name}`).";
fieldToEdit = "title";
break;
case "edit_description":
promptContent =
"Escribe la nueva **descripción** (puedes usar variables).";
fieldToEdit = "description";
break;
case "edit_color":
promptContent =
"Escribe el nuevo **color** en formato hexadecimal (ej: `#FF0000`).";
fieldToEdit = "color";
break;
case "edit_footer":
promptContent =
"Escribe el nuevo **texto del footer** (puedes usar variables).";
fieldToEdit = "footer";
break;
}
//@ts-ignore
const promptMessage = await i.channel.send(promptContent);
//@ts-ignore
const messageCollector = i.channel!.createMessageCollector({
//@ts-ignore
filter: (m: Message) => m.author.id === i.user.id,
max: 1,
time: 60000,
});
//@ts-ignore
messageCollector.on("collect", async (collectedMessage) => {
const newValue = collectedMessage.content;
if (fieldToEdit === "title") embedState.title = newValue;
if (fieldToEdit === "description") embedState.description = newValue;
if (fieldToEdit === "footer") embedState.footer = newValue;
if (fieldToEdit === "color") {
try {
const hex = newValue.replace("#", "");
embedState.color = parseInt(hex, 16);
} catch {
embedState.color = 0x5865f2;
}
}
await collectedMessage.delete();
await promptMessage.delete();
await editorMessage.edit({
embeds: [await renderPreview()],
components: generateButtonRows(false),
});
});
//@ts-ignore
messageCollector.on("end", async (collected) => {
if (collected.size === 0) {
await promptMessage.delete();
await editorMessage.edit({
components: generateButtonRows(false),
});
}
});
});
collector.on("end", async (_, reason) => {
if (reason === "time") {
const timeoutEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("Editor finalizado por inactividad.");
await editorMessage.edit({
embeds: [timeoutEmbed],
components: [],
});
}
});
}
},
};

View File

@@ -0,0 +1,276 @@
import { CommandMessage } from "../../../core/types/commands";
// @ts-ignore
import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, TextChannel, ChannelType } from "discord.js";
//@ts-ignore
import { ButtonStyle, ComponentType } from "discord.js";
import { replaceVars } from "../../../core/lib/vars";
export const command: CommandMessage = {
name: "editembed",
type: "message",
aliases: ["modembed", "updateembed"],
cooldown: 20,
// @ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embedName: string | null = args[0] ?? null;
if (!embedName) {
return message.reply(
"Debes proporcionar un nombre para el embed. Uso: `!editembed <nombre>`"
);
}
// 📌 Buscar en la base de datos
const existing = await client.prisma.embedConfig.findUnique({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
});
if (!existing) {
return message.reply("❌ No encontré un embed con ese nombre.");
}
// 📌 Estado inicial desde DB
let embedState: {
title?: string;
description?: string;
color?: number;
footer?: string;
} = {
title: existing.title ?? undefined,
description: existing.description ?? undefined,
color: existing.color ? parseInt(existing.color.replace("#", ""), 16) : 0x5865f2,
footer: existing.footerText ?? undefined,
};
// 📌 Función para renderizar preview
const renderPreview = async () => {
const preview = new EmbedBuilder().setColor(embedState.color ?? 0x5865f2);
if (embedState.title)
//@ts-ignore
preview.setTitle(await replaceVars(embedState.title, message.member));
if (embedState.description)
//@ts-ignore
preview.setDescription(await replaceVars(embedState.description, message.member));
if (embedState.footer)
preview.setFooter({
//@ts-ignore
text: await replaceVars(embedState.footer, message.member),
});
return preview;
};
const generateButtonRows = (disabled = false) => {
const primaryRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("edit_title")
.setLabel("Título")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("edit_description")
.setLabel("Descripción")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("edit_color")
.setLabel("Color")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled)
);
const secondaryRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("edit_footer")
.setLabel("Footer")
.setStyle(ButtonStyle.Secondary)
.setDisabled(disabled)
);
const controlRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("save_embed")
.setLabel("Guardar cambios")
.setStyle(ButtonStyle.Success)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("cancel_embed")
.setLabel("Cancelar")
.setStyle(ButtonStyle.Danger)
.setDisabled(disabled)
);
return [primaryRow, secondaryRow, controlRow];
};
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
const editorMessage = await channel.send({
embeds: [await renderPreview()],
components: generateButtonRows(),
});
const collector = editorMessage.createMessageComponentCollector({
componentType: ComponentType.Button,
time: 300000,
});
collector.on("collect", async (i) => {
if (i.user.id !== message.author.id) {
await i.reply({
content: "No puedes usar este menú.",
ephemeral: true,
});
return;
}
await i.deferUpdate();
await editorMessage.edit({ components: generateButtonRows(true) });
// Guardar cambios
if (i.customId === "save_embed") {
try {
const dataForDb = {
title: embedState.title,
description: embedState.description,
color: embedState.color ? `#${embedState.color.toString(16).padStart(6, '0')}` : null,
footerText: embedState.footer,
};
await client.prisma.embedConfig.update({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
data: dataForDb,
});
const saved = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle(`✅ Actualizado: ${embedName}`)
.setDescription("Los cambios fueron guardados en la base de datos.");
await editorMessage.edit({
embeds: [saved],
components: [],
});
} catch (e) {
const errorEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("❌ Error al Guardar")
.setDescription("No se pudo guardar en la base de datos. Revisa la consola.");
await editorMessage.edit({
embeds: [errorEmbed],
components: [],
});
console.error("Error de Prisma al actualizar el embed:", e);
}
collector.stop();
return;
}
// Cancelar
if (i.customId === "cancel_embed") {
await editorMessage.delete();
collector.stop();
return;
}
// Edición
let promptContent = "";
let fieldToEdit: "title" | "description" | "color" | "footer" | null =
null;
switch (i.customId) {
case "edit_title":
promptContent = "Escribe el nuevo **título** (puedes usar variables).";
fieldToEdit = "title";
break;
case "edit_description":
promptContent = "Escribe la nueva **descripción**.";
fieldToEdit = "description";
break;
case "edit_color":
promptContent = "Escribe el nuevo **color** en formato hexadecimal (ej: `#FF0000`).";
fieldToEdit = "color";
break;
case "edit_footer":
promptContent = "Escribe el nuevo **texto del footer**.";
fieldToEdit = "footer";
break;
}
//@ts-ignore
const promptMessage = await i.channel.send(promptContent);
//@ts-ignore
const messageCollector = i.channel!.createMessageCollector({
//@ts-ignore
filter: (m: Message) => m.author.id === i.user.id,
max: 1,
time: 60000,
});
//@ts-ignore
messageCollector.on("collect", async (collectedMessage) => {
const newValue = collectedMessage.content;
if (fieldToEdit === "title") embedState.title = newValue;
if (fieldToEdit === "description") embedState.description = newValue;
if (fieldToEdit === "footer") embedState.footer = newValue;
if (fieldToEdit === "color") {
try {
const hex = newValue.replace("#", "");
embedState.color = parseInt(hex, 16);
} catch {
embedState.color = 0x5865f2;
}
}
await collectedMessage.delete();
await promptMessage.delete();
await editorMessage.edit({
embeds: [await renderPreview()],
components: generateButtonRows(false),
});
});
//@ts-ignore
messageCollector.on("end", async (collected) => {
if (collected.size === 0) {
await promptMessage.delete();
await editorMessage.edit({
components: generateButtonRows(false),
});
}
});
});
collector.on("end", async (_, reason) => {
if (reason === "time") {
const timeoutEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("Editor finalizado por inactividad.");
await editorMessage.edit({
embeds: [timeoutEmbed],
components: [],
});
}
});
}
},
};

View File

@@ -0,0 +1,34 @@
import { CommandMessage } from "../../../core/types/commands";
export const command: CommandMessage = {
name: "embeddelete",
type: "message",
aliases: ["delembed", "removeembed"],
cooldown: 10,
//@ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embedName = args[0];
if (!embedName) {
return message.reply("Debes proporcionar el nombre del embed a eliminar. Uso: `!embeddelete <nombre>`");
}
try {
await client.prisma.embedConfig.delete({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
});
return message.reply(`✅ El embed **${embedName}** fue eliminado con éxito.`);
} catch {
return message.reply("❌ No encontré un embed con ese nombre.");
}
},
};

View File

@@ -0,0 +1,75 @@
import {CommandMessage} from "../../../core/types/commands";
import {
//@ts-ignore
ChannelType,
ContainerBuilder,
//@ts-ignore
MessageFlags,
SectionBuilder,
SeparatorBuilder,
//@ts-ignore
SeparatorSpacingSize,
TextChannel,
TextDisplayBuilder
} from "discord.js";
export const command: CommandMessage = {
name: "embedlist",
type: "message",
aliases: ["listembeds", "embeds"],
cooldown: 10,
//@ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embeds = await client.prisma.embedConfig.findMany({
where: { guildId: message.guildId! },
});
if (embeds.length === 0) {
return message.reply("📭 No hay ningún embed guardado en este servidor.");
}
const title = new TextDisplayBuilder()
.setContent('﹒⌒    Embed List    ╰୧﹒');
// Combina la lista de embeds en la misma sección que la miniatura
// para un mejor diseño.
//@ts-ignore
const embedListContent = embeds.map((e, i) => `**${i + 1}.** ${e.name}`).join("\n");
// Obtenemos la URL del icono de forma segura
const guildIconURL = message.guild?.iconURL({ forceStatic: false });
// Creamos la sección que contendrá el texto Y la miniatura
const mainSection = new SectionBuilder()
.addTextDisplayComponents(text => text.setContent(embedListContent)); // <--- Componente principal requerido
// Solo añadimos la miniatura si la URL existe
if (guildIconURL) {
//@ts-ignore
mainSection.setThumbnailAccessory(thumbnail => thumbnail
.setURL(guildIconURL)
.setDescription('Icono del servidor')
);
}
const separator = new SeparatorBuilder()
.setSpacing(SeparatorSpacingSize.Large)
.setDivider(false);
const container = new ContainerBuilder()
.setAccentColor(0x49225B)
.addTextDisplayComponents(title)
.addSeparatorComponents(separator)
.addSectionComponents(mainSection); // <--- Añadimos la sección ya completa
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
await channel.send({ components: [container], flags: MessageFlags.IsComponentsV2});
}
},
};

View File

@@ -0,0 +1,11 @@
import {CommandMessage} from "../../../core/types/commands";
export const command: CommandMessage = {
name: 'ping',
type: "message",
aliases: ['latency', 'pong'],
cooldown: 5,
run: async (message, args) => {
await message.reply('pong!')
}
}

View File

@@ -0,0 +1,45 @@
import {CommandMessage} from "../../../core/types/commands";
export const command: CommandMessage = {
name: 'test1',
type: "message",
cooldown: 5,
run: async (message, args) => {
//@ts-ignore
await message.channel.send({
"flags": 32768,
"components": [
{
"type": 17,
"components": [
{
"type": 10,
"content": "## ﹒⌒    🌹 Navegacion 🌹    ╰୧﹒"
},
{
"type": 14,
"spacing": 2,
"divider": false
},
{
"type": 9,
"components": [
{
"type": 10,
"content": "### Reglas dentro del Servidor"
}
],
"accessory": {
"style": 2,
"type": 5,
"label": "Ver",
"url": "https://discord.com/channels/1316592320954630144/1417682278762676264/1417901305434734656",
}
}
],
"accent_color": 4393549
}
]
})
}
}

View File

@@ -0,0 +1,51 @@
import {CommandMessage} from "../../../core/types/commands";
//@ts-ignore
import {
ButtonStyle, ChannelType,
ContainerBuilder,
MessageFlags,
SectionBuilder, SeparatorBuilder, SeparatorSpacingSize, TextChannel,
TextDisplayBuilder,
UserSelectMenuBuilder
} from "discord.js";
export const command: CommandMessage = {
name: 'settings',
type: "message",
aliases: ['options', 'stts'],
cooldown: 5,
run: async (message, args, client) => {
const server = await client.prisma.guild.findFirst({ where: { id: message.guild!.id } });
const title = new TextDisplayBuilder()
.setContent("## ﹒⌒    Settings Seɾveɾ    ╰୧﹒")
const description = new TextDisplayBuilder()
.setContent("Panel de Administracion del bot dentro del servidor.")
const sect = new TextDisplayBuilder()
.setContent("**Prefix del bot:** " + ` \`\`\`${server.prefix}\`\`\``)
const section = new SectionBuilder()
.addTextDisplayComponents(sect)
//@ts-ignore
.setButtonAccessory(button => button
.setCustomId('prefixsettings')
.setLabel('Prefix')
.setStyle(ButtonStyle.Primary),
)
const separator = new SeparatorBuilder()
.setSpacing(SeparatorSpacingSize.Large)
.setDivider(false);
const main = new ContainerBuilder()
.addTextDisplayComponents(title, description)
.addSeparatorComponents(separator)
.addSectionComponents(section)
//@ts-ignore
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
await channel.send({ components: [main], flags: MessageFlags.IsComponentsV2});
}
}
}

View File

@@ -0,0 +1,12 @@
import {CommandSlash} from "../../../core/types/commands";
export const command: CommandSlash = {
name: 'ping',
description: 'Ping',
type: "slash",
cooldown: 10,
run: async (interaction, client) => {
await interaction.reply('pong!')
}
}

View File

@@ -0,0 +1,22 @@
import type {ButtonInteraction} from "discord.js";
//@ts-ignore
import { ActionRowBuilder, Events, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'
export default {
customId: "prefixsettings",
run: async(interaction: ButtonInteraction) => {
const modal = new ModalBuilder()
.setCustomId('prefixsettingsmodal')
.setTitle('Prefix');
const prefixInput = new TextInputBuilder()
.setCustomId('prefixInput')
.setLabel("Change Prefix")
.setStyle(TextInputStyle.Short);
const secondActionRow = new ActionRowBuilder().addComponents(prefixInput);
modal.addComponents(secondActionRow);
await interaction.showModal(modal);
}
}

View File

@@ -0,0 +1,10 @@
import {ModalSubmitInteraction} from "discord.js";
export default {
customId: "prefixsettingsmodal",
run: async (interaction: ModalSubmitInteraction) => {
const newPrefix = interaction.fields.getTextInputValue("prefixInput")
}
}

View File

@@ -0,0 +1,41 @@
import { REST } from "discord.js";
// @ts-ignore
import { Routes } from "discord-api-types/v10";
import { commands } from "../loader";
export async function registeringCommands(): Promise<void> {
const commandsToRegister: any[] = [];
// Recorremos la Collection que ya cargó loadCommands()
for (const [name, cmd] of commands) {
if (cmd.type === "slash") {
commandsToRegister.push({
name: cmd.name,
description: cmd.description ?? "Sin descripción",
type: 1, // CHAT_INPUT
options: cmd.options ?? []
});
console.log(`✅ Preparado para registrar: ${cmd.name}`);
}
}
const rest = new REST().setToken(process.env.TOKEN ?? "");
try {
console.log(`🚀 Registrando ${commandsToRegister.length} comandos slash...`);
const data: any = await rest.put(
Routes.applicationGuildCommands(
process.env.CLIENT!,
process.env.guildTest!
),
{ body: commandsToRegister }
);
console.log(`${data.length} comandos registrados correctamente.`);
} catch (error) {
console.error("❌ Error registrando comandos:", error);
}
}

49
src/core/client.ts Normal file
View File

@@ -0,0 +1,49 @@
// @ts-ignore
import { Client, GatewayIntentBits } from 'discord.js';
// 1. Importa PrismaClient
// @ts-ignore
import { PrismaClient } from '@prisma/client';
process.loadEnvFile();
class Amayo extends Client {
public key: string;
// 2. Declara la propiedad prisma
public prisma: PrismaClient;
constructor() {
super({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMessageTyping
],
rest: {
retries: 10
}
});
this.key = process.env.TOKEN ?? '';
// 3. Instancia PrismaClient en el constructor
this.prisma = new PrismaClient();
}
async play () {
if(!this.key) {
return console.error('No key provided');
} else {
// Ejemplo de cómo usarías prisma antes de iniciar sesión
try {
await this.prisma.$connect();
console.log('Successfully connected to the database.');
await this.login(this.key);
} catch (error) {
console.error('Failed to connect to the database:', error);
}
}
}
}
export default Amayo;

49
src/core/components.ts Normal file
View File

@@ -0,0 +1,49 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { Collection } from "discord.js";
export const buttons: Collection<string, any> = new Collection<string, any>();
export const modals = new Collection<string, any>();
export const selectmenus = new Collection<string, any>();
export const contextmenus = new Collection<string, any>();
export function loadComponents(dir: string = path.join(__dirname, "..", "components")) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
loadComponents(fullPath); // recursivo
continue;
}
if (!file.endsWith(".ts") && !file.endsWith(".js")) continue;
const imported = require(fullPath);
const component = imported.default ?? imported;
if (!component?.customId) {
console.warn(`⚠️ Archivo ignorado: ${file} (no tiene "customId")`);
continue;
}
// Detectamos el tipo según la carpeta en la que está
if (fullPath.includes("buttons")) {
buttons.set(component.customId, component);
console.log(`🔘 Botón cargado: ${component.customId}`);
} else if (fullPath.includes("modals")) {
modals.set(component.customId, component);
console.log(`📄 Modal cargado: ${component.customId}`);
} else if (fullPath.includes("selectmenus")) {
selectmenus.set(component.customId, component);
console.log(`📜 SelectMenu cargado: ${component.customId}`);
} else if (fullPath.includes("contextmenu")) {
contextmenus.set(component.customId, component);
console.log(`📑 ContextMenu cargado: ${component.customId}`);
} else {
console.log(`⚠️ Componente desconocido: ${component.customId}`);
}
}
}

12
src/core/lib/vars.ts Normal file
View File

@@ -0,0 +1,12 @@
import {Guild, User} from "discord.js";
export async function replaceVars(text: string, user: User | undefined, guild:
Guild | undefined, stats: any) {
if(!text) return;
return text
.replace(/(user.name)/g, user!.username ?? '')
.replace(/(user.id)/g, user!.id ?? '')
.replace(/(user.mention)/g, `<@${user!.id}>`)
.replace(/(user.avatar)/g, user!.displayAvatarURL({ forceStatic: false }))
}

43
src/core/loader.ts Normal file
View File

@@ -0,0 +1,43 @@
import * as fs from "node:fs";
import path from "node:path";
import { Collection } from "discord.js";
export const commands = new Collection<string, any>();
export function loadCommands(dir: string = path.join(__dirname, '..', 'commands')) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
loadCommands(fullPath); // recursivo
continue;
}
if (!file.endsWith('.ts')) continue;
const imported = require(fullPath);
const command = imported.command ?? imported.default ?? imported;
if (!command?.data?.name && !command?.name) {
console.warn(`⚠️ Archivo ignorado: ${file} (no es un comando válido)`);
continue;
}
const name = command.data?.name ?? command.name;
console.log(`📦 Loading command: ${name}`);
// @ts-ignore
commands.set(name, command);
if (command.aliases?.length) {
for (const alias of command.aliases) {
commands.set(alias, command);
}
}
console.log(`✅ Cargado comando: ${name}`);
}
}

32
src/core/loaderEvents.ts Normal file
View File

@@ -0,0 +1,32 @@
import { bot } from "../main";
import path from "node:path";
import * as fs from "node:fs";
export function loadEvents(dir: string = path.join(__dirname, "../events")) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
loadEvents(fullPath); // recursión para subcarpetas
continue;
}
if (!file.endsWith(".ts") && !file.endsWith(".js")) continue;
const imported = require(fullPath);
const event = imported.default ?? imported;
if (!event?.name || !event?.execute) continue;
if (event.once) {
bot.once(event.name, (...args: any[]) => event.execute(...args));
} else {
bot.on(event.name, (...args: any[]) => event.execute(...args));
}
console.log(`Evento cargado: ${event.name}`);
}
}

13
src/core/redis.ts Normal file
View File

@@ -0,0 +1,13 @@
import { createClient } from "redis";
export const redis = createClient({
url: process.env.REDIS_URL,
})
redis.on("error", (err: any) => console.error("Redis error:", err));
redis.on("connect", () => console.log("✅ Conectado a Redis"));
redis.on("reconnecting", () => console.warn("♻️ Reintentando conexión Redis"));
export async function redisConnect () {
if (!redis.isOpen) await redis.connect();
}

View File

@@ -0,0 +1,19 @@
import type {ChatInputCommandInteraction, Client, Message} from "discord.js";
import Amayo from "../client";
export interface CommandMessage {
name: string;
type: 'message';
aliases?: string[];
cooldown?: number;
run: (message: Message, args: string[], client: Amayo) => Promise<void>;
}
export interface CommandSlash {
name: string;
description: string;
type: 'slash';
options?: string[];
cooldown?: number;
run: (i: ChatInputCommandInteraction, client: Client) => Promise<void>;
}

View File

@@ -0,0 +1,7 @@
import type {ButtonInteraction} from "discord.js";
export interface button {
customId: string;
run: (interaction: ButtonInteraction) => Promise<void>;
}

View File

@@ -0,0 +1,53 @@
import { bot } from "../main";
import type { BaseInteraction } from "discord.js";
import { Events } from "discord.js";
import { redis } from "../core/redis";
import { commands } from "../core/loader";
import { buttons, modals, selectmenus } from "../core/components";
bot.on(Events.InteractionCreate, async (interaction: BaseInteraction) => {
try {
// 🔹 Slash commands
if (interaction.isChatInputCommand()) {
const cmd = commands.get(interaction.commandName);
if (!cmd) return;
const cooldown = Math.floor(Number(cmd.cooldown) || 0);
if (cooldown > 0) {
const key = `cooldown:${cmd.name}:${interaction.user.id}`;
const ttl = await redis.ttl(key);
if (ttl > 0) {
return interaction.reply(`⏳ Espera ${ttl}s antes de volver a usar **${cmd.name}**.`);
}
await redis.set(key, "1", { EX: cooldown });
}
await cmd.run(interaction, bot);
}
// 🔹 Botones
if (interaction.isButton()) {
//@ts-ignore
const btn = buttons.get(interaction.customId);
if (btn) await btn.run(interaction, bot);
}
// 🔹 Select menus
if (interaction.isStringSelectMenu()) {
const menu = selectmenus.get(interaction.customId);
if (menu) await menu.run(interaction, bot);
}
// 🔹 Modales
if (interaction.isModalSubmit()) {
const modal = modals.get(interaction.customId);
if (modal) await modal.run(interaction, bot);
}
} catch (error) {
console.error(error);
if (interaction.isRepliable()) {
await interaction.reply({ content: "❌ Hubo un error ejecutando la interacción.", ephemeral: true });
}
}
});

View File

@@ -0,0 +1,40 @@
import {bot} from "../main";
import {Events} from "discord.js";
import {redis} from "../core/redis";
import {commands} from "../core/loader";
bot.on(Events.MessageCreate, async (message) => {
if (message.author.bot) return;
const server = await bot.prisma.guild.findFirst({ where: { id: message.guild!.id } }) || "!";
const PREFIX = server.prefix
if (!message.content.startsWith(PREFIX)) return;
const [cmdName, ...args] = message.content.slice(PREFIX.length).trim().split(/\s+/);
console.log(cmdName);
const command = commands.get(cmdName);
if (!command) return;
const cooldown = Math.floor(Number(command.cooldown) || 0);
if (cooldown > 0) {
const key = `cooldown:${command.name}:${message.author.id}`;
const ttl = await redis.ttl(key);
console.log(`Key: ${key}, TTL: ${ttl}`);
if (ttl > 0) {
return message.reply(`⏳ Espera ${ttl}s antes de volver a usar **${command.name}**.`);
}
// SET con expiración correcta para redis v4+
await redis.set(key, "1", { EX: cooldown });
}
try {
await command.run(message, args, message.client);
} catch (error) {
console.error(error);
await message.reply("❌ Hubo un error ejecutando el comando.");
}
})

6
src/events/ready.ts Normal file
View File

@@ -0,0 +1,6 @@
import {bot} from "../main";
import {Events} from "discord.js";
bot.on(Events.ClientReady, () => {
console.log("Ready!");
})

28
src/main.ts Normal file
View File

@@ -0,0 +1,28 @@
import Amayo from "./core/client";
import { loadCommands } from "./core/loader";
import { loadEvents } from "./core/loaderEvents";
import { redisConnect } from "./core/redis";
import { registeringCommands } from "./core/api/discordAPI";
import {loadComponents} from "./core/components";
export const bot = new Amayo();
async function bootstrap() {
console.log("🚀 Iniciando bot...");
loadCommands(); // 1⃣ Cargar comandos en la Collection
loadComponents()
loadEvents(); // 2⃣ Cargar eventos
await registeringCommands(); // 3⃣ Registrar los slash en Discord
await redisConnect(); // 4⃣ Conectar Redis
await bot.play();
console.log("✅ Bot conectado a Discord");
}
bootstrap().catch((err) => {
console.error("❌ Error en el arranque:", err);
process.exit(1);
});

13
tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2024",
"module": "ESNext",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["src"]
}