diff --git a/.gitignore b/.gitignore index b2c8fcd..bc1fbea 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ logs/ ecosystem.config.js dist/ + +src/local/ + +storage/oredic/dumps/*.txt diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4755ecc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "printWidth": 110, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "semi": true, + "trailingComma": "all", + "quoteProps": "consistent", + "arrowParens": "always", + "checkIgnorePragma": true +} diff --git a/eslint.config.mts b/eslint.config.mts new file mode 100644 index 0000000..5adb7a0 --- /dev/null +++ b/eslint.config.mts @@ -0,0 +1,63 @@ +import eslint from "@eslint/js"; +import { defineConfig } from "eslint/config"; +import nodePlugin from "eslint-plugin-n"; +import simpleImportSort from "eslint-plugin-simple-import-sort"; +import sonarJs from "eslint-plugin-sonarjs"; +import unusedImports from "eslint-plugin-unused-imports"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default defineConfig( + { + ignores: ["dist/**", "config/**"], + }, + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.es2021, + }, + parserOptions: { + project: "./tsconfig.eslint.json", + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + plugins: { + "unused-imports": unusedImports, + "n": nodePlugin, + "sonarjs": sonarJs, + "importsort": simpleImportSort, + }, + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + "@typescript-eslint/await-thenable": "error", + "@typescript-eslint/return-await": "error", + "@typescript-eslint/strict-boolean-expressions": "warn", + "@typescript-eslint/switch-exhaustiveness-check": "error", + "@typescript-eslint/consistent-type-imports": "error", + "no-return-await": "off", + "unused-imports/no-unused-imports": "error", + "no-console": "error", + "eqeqeq": ["error", "always"], + "@typescript-eslint/no-explicit-any": "warn", + "no-duplicate-imports": "error", + "n/no-deprecated-api": "warn", + "n/prefer-global/process": "warn", + "sonarjs/no-duplicate-string": "warn", + "sonarjs/no-identical-functions": "warn", + "sonarjs/cognitive-complexity": ["warn", 15], + "no-param-reassign": "warn", + "no-implicit-coercion": "warn", + "no-warning-comments": ["warn", { terms: ["todo", "fixme"], location: "start" }], + "importsort/imports": "error", + "importsort/exports": "error", + }, + }, +); diff --git a/fsconfig.json b/fsconfig.json new file mode 100644 index 0000000..e69de29 diff --git a/main.mjs b/main.mjs deleted file mode 100644 index 6ca5d30..0000000 --- a/main.mjs +++ /dev/null @@ -1,3 +0,0 @@ -console.warn( - "main.mjs is deprecated. The server entrypoint now lives in index.ts." -); diff --git a/package-lock.json b/package-lock.json index 2436238..889217b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1596 +1,3218 @@ { - "name": "grok-server", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "grok-server", - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "discord.js": "^14.23.2", - "dotenv": "^17.2.3", - "express": "^5.1.0", - "fs": "^0.0.1-security", - "openai": "^6.3.0", - "redis": "^5.8.3", - "ts-node": "^10.9.2", - "typescript": "^5.9.3", - "zod": "^4.1.12" - }, - "devDependencies": { - "@types/express": "^5.0.3", - "ts-node": "^10.9.2", - "typescript": "^5.9.3" - } - }, - "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.12.2", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.12.2.tgz", - "integrity": "sha512-AugKmrgRJOHXEyMkABH/hXpAmIBKbYokCEl9VAM4Kh6FvkdobQ+Zhv7AR6K+y5hS7+VQ7gKXPYCe1JQmV07H1g==", - "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.26", - "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/@redis/bloom": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.8.3.tgz", - "integrity": "sha512-1eldTzHvdW3Oi0TReb8m1yiFt8ZwyF6rv1NpZyG5R4TpCwuAdKQetBKoCw7D96tNFgsVVd6eL+NaGZZCqhRg4g==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.8.3" - } - }, - "node_modules/@redis/client": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.8.3.tgz", - "integrity": "sha512-MZVUE+l7LmMIYlIjubPosruJ9ltSLGFmJqsXApTqPLyHLjsJUSAbAJb/A3N34fEqean4ddiDkdWzNu4ZKPvRUg==", - "license": "MIT", - "peer": true, - "dependencies": { - "cluster-key-slot": "1.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@redis/json": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.8.3.tgz", - "integrity": "sha512-DRR09fy/u8gynHGJ4gzXYeM7D8nlS6EMv5o+h20ndTJiAc7RGR01fdk2FNjnn1Nz5PjgGGownF+s72bYG4nZKQ==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.8.3" - } - }, - "node_modules/@redis/search": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.8.3.tgz", - "integrity": "sha512-EMIvEeGRR2I0BJEz4PV88DyCuPmMT1rDtznlsHY3cKSDcc9vj0Q411jUnX0iU2vVowUgWn/cpySKjpXdZ8m+5g==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.8.3" - } - }, - "node_modules/@redis/time-series": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.8.3.tgz", - "integrity": "sha512-5Jwy3ilsUYQjzpE7WZ1lEeG1RkqQ5kHtwV1p8yxXHSEmyUbC/T/AVgyjMcm52Olj/Ov/mhDKjx6ndYUi14bXsw==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@redis/client": "^5.8.3" - } - }, - "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/@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/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", - "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", - "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~7.14.0" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", - "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", - "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "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.7", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", - "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.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/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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.30", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.30.tgz", - "integrity": "sha512-KhAqlBrg+rVK+Ob7INMF5o63yW4/GUzRatG/AjyVsIO8lgcLyR8qCl2HokIVzWwmzkJYG0CEPXsKMOqau3E8NA==", - "license": "MIT", - "workspaces": [ - "scripts/actions/documentation" - ] - }, - "node_modules/discord.js": { - "version": "14.23.2", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.23.2.tgz", - "integrity": "sha512-tU2NFr823X3TXEc8KyR/4m296KLxPai4nirN3q9kHCpY4TKj96n9lHZnyLzRNMui8EbL07jg9hgH2PWWfKMGIg==", - "license": "Apache-2.0", - "dependencies": { - "@discordjs/builders": "^1.12.1", - "@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.29", - "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": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "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/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openai": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.3.0.tgz", - "integrity": "sha512-E6vOGtZvdcb4yXQ5jXvDlUG599OhIkb/GjBLZXS+qk0HF+PJReIldEc9hM8Ft81vn+N6dRdFRb7BZNK8bbvXrw==", - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/redis": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/redis/-/redis-5.8.3.tgz", - "integrity": "sha512-MfSrfV6+tEfTw8c4W0yFp6XWX8Il4laGU7Bx4kvW4uiYM1AuZ3KGqEGt1LdQHeD1nEyLpIWetZ/SpY3kkbgrYw==", - "license": "MIT", - "dependencies": { - "@redis/bloom": "5.8.3", - "@redis/client": "5.8.3", - "@redis/json": "5.8.3", - "@redis/search": "5.8.3", - "@redis/time-series": "5.8.3" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "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/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "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.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "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 + "name": "hog-api", + "version": "0.3.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hog-api", + "version": "0.3.0", + "license": "MIT", + "dependencies": { + "discord.js": "^14.23.2", + "dotenv": "^17.2.3", + "eslint": "^9.39.2", + "eslint-plugin-n": "^17.23.1", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-sonarjs": "^3.0.5", + "eslint-plugin-unused-imports": "^4.3.0", + "express": "^5.1.0", + "fs": "^0.0.1-security", + "jiti": "^2.6.1", + "openai": "^6.3.0", + "prettier": "^3.7.4", + "redis": "^5.8.3", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.50.1", + "zod": "^4.1.12" + }, + "devDependencies": { + "@types/express": "^5.0.3", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + } + }, + "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.12.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.12.2.tgz", + "integrity": "sha512-AugKmrgRJOHXEyMkABH/hXpAmIBKbYokCEl9VAM4Kh6FvkdobQ+Zhv7AR6K+y5hS7+VQ7gKXPYCe1JQmV07H1g==", + "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.26", + "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/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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/@redis/bloom": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.8.3.tgz", + "integrity": "sha512-1eldTzHvdW3Oi0TReb8m1yiFt8ZwyF6rv1NpZyG5R4TpCwuAdKQetBKoCw7D96tNFgsVVd6eL+NaGZZCqhRg4g==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.8.3" + } + }, + "node_modules/@redis/client": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.8.3.tgz", + "integrity": "sha512-MZVUE+l7LmMIYlIjubPosruJ9ltSLGFmJqsXApTqPLyHLjsJUSAbAJb/A3N34fEqean4ddiDkdWzNu4ZKPvRUg==", + "license": "MIT", + "peer": true, + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@redis/json": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.8.3.tgz", + "integrity": "sha512-DRR09fy/u8gynHGJ4gzXYeM7D8nlS6EMv5o+h20ndTJiAc7RGR01fdk2FNjnn1Nz5PjgGGownF+s72bYG4nZKQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.8.3" + } + }, + "node_modules/@redis/search": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.8.3.tgz", + "integrity": "sha512-EMIvEeGRR2I0BJEz4PV88DyCuPmMT1rDtznlsHY3cKSDcc9vj0Q411jUnX0iU2vVowUgWn/cpySKjpXdZ8m+5g==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.8.3" + } + }, + "node_modules/@redis/time-series": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.8.3.tgz", + "integrity": "sha512-5Jwy3ilsUYQjzpE7WZ1lEeG1RkqQ5kHtwV1p8yxXHSEmyUbC/T/AVgyjMcm52Olj/Ov/mhDKjx6ndYUi14bXsw==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.8.3" + } + }, + "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/@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/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.14.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", + "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", + "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "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/@typescript-eslint/eslint-plugin": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", + "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/type-utils": "8.50.1", + "@typescript-eslint/utils": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.50.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", + "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", + "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.50.1", + "@typescript-eslint/types": "^8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", + "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", + "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", + "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", + "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", + "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.50.1", + "@typescript-eslint/tsconfig-utils": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", + "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", + "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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.30", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.30.tgz", + "integrity": "sha512-KhAqlBrg+rVK+Ob7INMF5o63yW4/GUzRatG/AjyVsIO8lgcLyR8qCl2HokIVzWwmzkJYG0CEPXsKMOqau3E8NA==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/discord.js": { + "version": "14.23.2", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.23.2.tgz", + "integrity": "sha512-tU2NFr823X3TXEc8KyR/4m296KLxPai4nirN3q9kHCpY4TKj96n9lHZnyLzRNMui8EbL07jg9hgH2PWWfKMGIg==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.12.1", + "@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.29", + "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": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-n": { + "version": "17.23.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.23.1.tgz", + "integrity": "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.5.0", + "enhanced-resolve": "^5.17.1", + "eslint-plugin-es-x": "^7.8.0", + "get-tsconfig": "^4.8.1", + "globals": "^15.11.0", + "globrex": "^0.1.2", + "ignore": "^5.3.2", + "semver": "^7.6.3", + "ts-declaration-location": "^1.0.6" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", + "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", + "license": "MIT", + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-plugin-sonarjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.5.tgz", + "integrity": "sha512-dI62Ff3zMezUToi161hs2i1HX1ie8Ia2hO0jtNBfdgRBicAG4ydy2WPt0rMTrAe3ZrlqhpAO3w1jcQEdneYoFA==", + "license": "LGPL-3.0-only", + "dependencies": { + "@eslint-community/regexpp": "4.12.1", + "builtin-modules": "3.3.0", + "bytes": "3.1.2", + "functional-red-black-tree": "1.0.1", + "jsx-ast-utils-x": "0.1.0", + "lodash.merge": "4.6.2", + "minimatch": "9.0.5", + "scslre": "0.3.0", + "semver": "7.7.2", + "typescript": ">=5" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz", + "integrity": "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==", + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "license": "ISC" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT" + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "license": "MIT" + }, + "node_modules/jsx-ast-utils-x": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils-x/-/jsx-ast-utils-x-0.1.0.tgz", + "integrity": "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.3.0.tgz", + "integrity": "sha512-E6vOGtZvdcb4yXQ5jXvDlUG599OhIkb/GjBLZXS+qk0HF+PJReIldEc9hM8Ft81vn+N6dRdFRb7BZNK8bbvXrw==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/redis": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.8.3.tgz", + "integrity": "sha512-MfSrfV6+tEfTw8c4W0yFp6XWX8Il4laGU7Bx4kvW4uiYM1AuZ3KGqEGt1LdQHeD1nEyLpIWetZ/SpY3kkbgrYw==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.8.3", + "@redis/client": "5.8.3", + "@redis/json": "5.8.3", + "@redis/search": "5.8.3", + "@redis/time-series": "5.8.3" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-declaration-location": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", + "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": ">=4.0.0" + } + }, + "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/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz", + "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "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.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "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/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "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" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } - } - }, - "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" - } - }, - "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } - } } diff --git a/package.json b/package.json index 3103b5c..af0ab6c 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,49 @@ { - "name": "grok-server", - "version": "0.2.0", - "description": "just a simple express api for running grok on leveret", - "main": "dist/server.js", - "scripts": { - "build": "tsc", - "dev": "ts-node src/server.ts" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/D-Alessian/grok-server.git" - }, - "author": "D-Alessian", - "license": "MIT", - "bugs": { - "url": "https://github.com/D-Alessian/grok-server/issues" - }, - "homepage": "https://github.com/D-Alessian/grok-server#readme", - "dependencies": { - "discord.js": "^14.23.2", - "dotenv": "^17.2.3", - "express": "^5.1.0", - "fs": "^0.0.1-security", - "openai": "^6.3.0", - "redis": "^5.8.3", - "ts-node": "^10.9.2", - "typescript": "^5.9.3", - "zod": "^4.1.12" - }, - "devDependencies": { - "@types/express": "^5.0.3", - "ts-node": "^10.9.2", - "typescript": "^5.9.3" - } + "name": "hog-api", + "version": "0.3.0", + "description": "just a simple express api for running grok on leveret", + "main": "dist/Server.js", + "scripts": { + "build": "tsc", + "dev": "ts-node src/Server.ts", + "format": "prettier --write .", + "format:check": "prettier --check .", + "lint:check": "eslint .", + "lint:fix": "eslint . --fix", + "lint:report": "eslint \"src/**/*.ts\" --format json --output-file temp/lint/eslint_report.json" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/D-Alessian/grok-server.git" + }, + "author": "D-Alessian", + "license": "MIT", + "bugs": { + "url": "https://github.com/D-Alessian/grok-server/issues" + }, + "homepage": "https://github.com/D-Alessian/grok-server#readme", + "dependencies": { + "discord.js": "^14.23.2", + "dotenv": "^17.2.3", + "eslint": "^9.39.2", + "eslint-plugin-n": "^17.23.1", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-sonarjs": "^3.0.5", + "eslint-plugin-unused-imports": "^4.3.0", + "express": "^5.1.0", + "fs": "^0.0.1-security", + "jiti": "^2.6.1", + "openai": "^6.3.0", + "prettier": "^3.7.4", + "redis": "^5.8.3", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.50.1", + "zod": "^4.1.12" + }, + "devDependencies": { + "@types/express": "^5.0.3", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + } } diff --git a/src/Events.ts b/src/Events.ts new file mode 100644 index 0000000..6af0b87 --- /dev/null +++ b/src/Events.ts @@ -0,0 +1,75 @@ +import express from "express"; +import fs from "fs"; +import path from "path"; + +import { BotEventHandler } from "./bot/EventHandler"; +import { getDiscordClient, initDiscordClient } from "./clients/DiscordClient"; +import { initGrokClient } from "./clients/GrokClient"; +import { getRedisClient, initRedisClient } from "./clients/RedisClient"; +import { getDbHandler, initDbHandler } from "./db/DbHandler"; +import { initFs } from "./helpers/files/FileSystem"; +import { getLogger, initLogger } from "./helpers/Logger"; +import { startTimer } from "./helpers/Timer"; +import { routes } from "./routes"; +import { startGlobalRateLimitIncrement, startUserRateLimitIncrements } from "./services/rateLimits"; +import { ErrorProne } from "./utils/parentClasses/ErrorProne"; + +export class Events extends ErrorProne { + constructor() { + super(); + } + + static initEssentials() { + startTimer("main"); + initLogger(); + initFs(JSON.parse(fs.readFileSync(path.join(process.cwd(), "fsconfig.json"), "utf-8"))); + } + + static async initDb() { + initDbHandler(); + await getDbHandler().init(); + } + + static async initClients() { + initRedisClient(); + await getRedisClient().connect(); + initGrokClient(); + } + + static async initServices() { + startGlobalRateLimitIncrement(); + await startUserRateLimitIncrements(); + } + + static async initDiscord() { + initDiscordClient(); + const client = getDiscordClient().client; + const eventHandler = new BotEventHandler(); + + client.once("clientReady", () => eventHandler.onReady()); + client.on("guildMemberAdd", (member) => eventHandler.onNewMember(member)); + client.on("guildMemberRemove", (member) => eventHandler.onRemoveMember(member)); + + await getDiscordClient().connect(); + } + + static async initExpress() { + const app = express(); + + app.use(routes()); + + app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => { + getLogger().simpleLog("error", `Unhandled error: ${err.message}`); + console.error(err.stack); + + if (!res.headersSent) { + res.status(500).json({ + error: "Internal Server Error", + message: process.env.NODE_ENV === "development" ? err.message : "Something went wrong", + }); + } + }); + + return app; + } +} diff --git a/src/Server.ts b/src/Server.ts new file mode 100644 index 0000000..ec0487f --- /dev/null +++ b/src/Server.ts @@ -0,0 +1,52 @@ +import type { Express } from "express"; + +import { config } from "./config/config"; +import { Events } from "./Events"; +import { getLogger } from "./helpers/Logger"; +import { stopTimer } from "./helpers/Timer"; +import { ErrorProne } from "./utils/parentClasses/ErrorProne"; + +class Server extends ErrorProne { + app: Express | null; + + constructor() { + super(); + this.app = null; + } + + async main() { + Events.initEssentials(); + + getLogger().formattingLog("Clients Init"); + await Events.initClients(); + + getLogger().formattingLog("DB Init"); + await Events.initDb(); + + getLogger().formattingLog("Discord Bot Init"); + await Events.initDiscord(); + + getLogger().formattingLog("Services Init"); + await Events.initServices(); + + getLogger().formattingLog("App Init"); + this.app = await Events.initExpress(); + } +} + +const server = new Server(); +server.main(); + +if (!server.app) { + throw new Error("Critical Error: Could not get express app"); +} + +server.app.listen(config.PORT, config.RUNNING_IP, () => { + getLogger().formattingLog("Server Info"); + getLogger().simpleLog("info", `Server is running at http://${config.RUNNING_IP}:${config.PORT}`); + getLogger().simpleLog( + "telemetry", + `Server took ${stopTimer("main").getTime("auto", 3).formatted} to start`, + ); + getLogger().formattingLog("Server Ready"); +}); diff --git a/src/bot/EventHandler.ts b/src/bot/EventHandler.ts index f7b53c4..5839f6e 100644 --- a/src/bot/EventHandler.ts +++ b/src/bot/EventHandler.ts @@ -1,67 +1,63 @@ -import { GuildMember, PartialGuildMember, Guild } from "discord.js"; +import type { Guild, GuildMember, PartialGuildMember } from "discord.js"; + import { getDiscordClient } from "../clients/DiscordClient"; -import { getLogger } from "../utils/Logger"; import { config, env } from "../config/config"; import { getDbHandler } from "../db/DbHandler"; +import { getLogger } from "../helpers/Logger"; import { findDcUsernameById } from "../utils/bot/usernames"; export class BotEventHandler { - client; - guild: Guild | null = null; - dbHandler; + client; + guild: Guild | null = null; + dbHandler; + + constructor() { + this.client = getDiscordClient().client; + this.dbHandler = getDbHandler(); + } - constructor() { - this.client = getDiscordClient().client; - this.dbHandler = getDbHandler(); - } + private async ensureGuild(): Promise { + if (this.guild) return this.guild; - private async ensureGuild(): Promise { - if (this.guild) return this.guild; + const guild = this.client.guilds.cache.get(env.SERVER_ID); + if (!guild) { + throw new Error(`Failed to fetch Server ID: ${env.SERVER_ID}`); + } - const guild = this.client.guilds.cache.get(env.SERVER_ID); - if (!guild) { - throw new Error(`Failed to fetch Server ID: ${env.SERVER_ID}`); + this.guild = guild; + return guild; } - this.guild = guild; - return guild; - } - - async onReady() { - const guild = await this.ensureGuild(); + async onReady() { + const guild = await this.ensureGuild(); - getLogger().simpleLog( - "info", - `Discord Bot ready as ${this.client.user?.tag}` - ); + getLogger().simpleLog("info", `Discord Bot ready as ${this.client.user?.tag}`); - const members = await guild.members.fetch(); - let memberIds: string[] = []; + const members = await guild.members.fetch(); + const memberIds: string[] = []; - members.forEach((value, key) => { - memberIds.push(key); - }); + members.forEach((value, key) => { + memberIds.push(key); + }); - this.dbHandler.createHog(memberIds); - } + this.dbHandler.createHog(memberIds); + } - async onNewMember(member: GuildMember | PartialGuildMember) { - const memberId = member.id; - this.dbHandler.addToHog(memberId); - getLogger().simpleLog( - "info", - `${await findDcUsernameById(memberId)} joined ${ - config.DISCORD_SERVER_NAME - }` - ); - } + async onNewMember(member: GuildMember | PartialGuildMember) { + const memberId = member.id; + this.dbHandler.addToHog(memberId); + getLogger().simpleLog( + "telemetry", + `${await findDcUsernameById(memberId)} joined ${config.DISCORD_SERVER_NAME}`, + ); + } - async onRemoveMember(member: GuildMember | PartialGuildMember) { - const memberId = member.id; - this.dbHandler.removeFromHog(memberId); - getLogger().simpleLog( - "warn", - `${await findDcUsernameById(memberId)} left ${config.DISCORD_SERVER_NAME}` - ); - } + async onRemoveMember(member: GuildMember | PartialGuildMember) { + const memberId = member.id; + this.dbHandler.removeFromHog(memberId); + getLogger().simpleLog( + "warn", + `${await findDcUsernameById(memberId)} left ${config.DISCORD_SERVER_NAME}`, + ); + } } diff --git a/src/clients/DiscordClient.ts b/src/clients/DiscordClient.ts index f55c37d..27f9ebf 100644 --- a/src/clients/DiscordClient.ts +++ b/src/clients/DiscordClient.ts @@ -1,47 +1,42 @@ import { Client, GatewayIntentBits } from "discord.js"; -import { getLogger } from "../utils/Logger"; + import { env } from "../config/config"; +import { getLogger } from "../helpers/Logger"; let discordClient: DiscordClient | null = null; export class DiscordClient { - client: Client; - - constructor() { - this.client = this.buildClient(); - } - - static gatewayIntents = [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMembers, - ]; - - buildClient() { - return new Client({ - intents: DiscordClient.gatewayIntents, - }); - } - - async connect() { - try { - await this.client.login(env.DISCORD_BOT_TOKEN); - getLogger().simpleLog("success", "Discord Client Connected Successfully"); - } catch (err) { - getLogger().simpleLog("error", `Failed to connect to Discord: ${err}`); + client: Client; + + constructor() { + this.client = this.buildClient(); + } + + static gatewayIntents = [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers]; + + buildClient() { + return new Client({ + intents: DiscordClient.gatewayIntents, + }); + } + + async connect() { + try { + await this.client.login(env.DISCORD_BOT_TOKEN); + getLogger().simpleLog("success", "Discord Client Connected Successfully"); + } catch (err) { + getLogger().simpleLog("error", `Failed to connect to Discord: ${err}`); + } } - } } export function initDiscordClient(): DiscordClient { - if (discordClient) return discordClient; - discordClient = new DiscordClient(); - return discordClient; + if (discordClient) return discordClient; + discordClient = new DiscordClient(); + return discordClient; } export function getDiscordClient(): DiscordClient { - if (!discordClient) - throw new Error( - "Discord Client not initialized. Call initDiscordClient() first." - ); - return discordClient; + if (!discordClient) throw new Error("Discord Client not initialized. Call initDiscordClient() first."); + return discordClient; } diff --git a/src/clients/GrokClient.ts b/src/clients/GrokClient.ts index 52e6b1b..a6ab0be 100644 --- a/src/clients/GrokClient.ts +++ b/src/clients/GrokClient.ts @@ -1,36 +1,34 @@ import OpenAI from "openai"; + import { config, env } from "../config/config"; -import { getLogger } from "../utils/Logger"; +import { getLogger } from "../helpers/Logger"; let grokClient: GrokClient | null = null; export class GrokClient { - client: OpenAI; + client: OpenAI; - constructor() { - this.client = this.buildClient(); - } + constructor() { + this.client = this.buildClient(); + } - buildClient() { - getLogger().simpleLog("success", "Building Grok Client"); - return new OpenAI({ - apiKey: env.GROK_API_KEY, - baseURL: "https://api.x.ai/v1", - timeout: config.GROK_TIMEOUT, - }); - } + buildClient() { + getLogger().simpleLog("success", "Building Grok Client"); + return new OpenAI({ + apiKey: env.GROK_API_KEY, + baseURL: "https://api.x.ai/v1", + timeout: config.GROK_TIMEOUT, + }); + } } export function initGrokClient(): GrokClient { - if (grokClient) return grokClient; - grokClient = new GrokClient(); - return grokClient; + if (grokClient) return grokClient; + grokClient = new GrokClient(); + return grokClient; } export function getGrokClient(): GrokClient { - if (!grokClient) - throw new Error( - "Grok Client not initialized. Call initGrokClient() first." - ); - return grokClient; + if (!grokClient) throw new Error("Grok Client not initialized. Call initGrokClient() first."); + return grokClient; } diff --git a/src/clients/RedisClient.ts b/src/clients/RedisClient.ts index c679c11..ba6b3c0 100644 --- a/src/clients/RedisClient.ts +++ b/src/clients/RedisClient.ts @@ -1,52 +1,48 @@ import { createClient } from "redis"; + import { env } from "../config/config"; -import { getLogger } from "../utils/Logger"; +import { getLogger } from "../helpers/Logger"; let redisClient: RedisClient | null = null; export class RedisClient { - client: ReturnType; - - constructor() { - this.client = this.buildClient(); - } - - buildClient() { - const client = createClient({ - username: env.REDIS_USERNAME, - password: env.REDIS_PASSWORD, - socket: { - host: env.REDIS_HOST, - port: env.REDIS_PORT, - tls: false, - }, - }); - client.on("error", (err) => - getLogger().simpleLog("error", `Redis Client Error ${err}`) - ); - return client; - } - - async connect() { - try { - await this.client.connect(); - getLogger().simpleLog("success", "Redis Client Connected Successfully"); - } catch (err) { - getLogger().simpleLog("error", `Failed to connect to Redis: ${err}`); + client: ReturnType; + + constructor() { + this.client = this.buildClient(); + } + + buildClient() { + const client = createClient({ + username: env.REDIS_USERNAME, + password: env.REDIS_PASSWORD, + socket: { + host: env.REDIS_HOST, + port: env.REDIS_PORT, + tls: false, + }, + }); + client.on("error", (err) => getLogger().simpleLog("error", `Redis Client Error ${err}`)); + return client; + } + + async connect() { + try { + await this.client.connect(); + getLogger().simpleLog("success", "Redis Client Connected Successfully"); + } catch (err) { + getLogger().simpleLog("error", `Failed to connect to Redis: ${err}`); + } } - } } export function initRedisClient(): RedisClient { - if (redisClient) return redisClient; - redisClient = new RedisClient(); - return redisClient; + if (redisClient) return redisClient; + redisClient = new RedisClient(); + return redisClient; } export function getRedisClient(): RedisClient { - if (!redisClient) - throw new Error( - "Redis Client not initialized. Call initRedisClient() first." - ); - return redisClient; + if (!redisClient) throw new Error("Redis Client not initialized. Call initRedisClient() first."); + return redisClient; } diff --git a/src/config/config.ts b/src/config/config.ts index d4b7bd6..515ae0e 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,17 +1,11 @@ -import path from "path"; -import fs from "fs"; import dotenv from "dotenv"; -import { Config, Env, FiltersConfig } from "./schema"; -import { - validateConfigs, - validateEnvs, - validateFiltersConfigs, -} from "./validate"; + import { RAW_CONFIG, RAW_FILTERS_CONFIG } from "../loaders/storage"; +import type { Config, Env, FiltersConfig } from "./schema"; +import { validateConfigs, validateEnvs, validateFiltersConfigs } from "./validate"; -dotenv.config(); +dotenv.config({ quiet: true }); export const env: Env = validateEnvs(); export const config: Config = validateConfigs(RAW_CONFIG); -export const filtersConfig: FiltersConfig = - validateFiltersConfigs(RAW_FILTERS_CONFIG); +export const filtersConfig: FiltersConfig = validateFiltersConfigs(RAW_FILTERS_CONFIG); diff --git a/src/config/fileSchema.ts b/src/config/fileSchema.ts index 5e7f1f5..b6e3bc9 100644 --- a/src/config/fileSchema.ts +++ b/src/config/fileSchema.ts @@ -1,59 +1,59 @@ -import z, { base64, string } from "zod"; +import z, { string } from "zod"; export const EnvFileSchema = z.object({ - GROK_API_KEY: z.string().min(1), - DISCORD_BOT_TOKEN: z.string().min(1), - NODE_ENV: z - .enum(["development", "production", "test"]) - .default("development"), - LEVERET_PUB_KEY_B64: z.base64(), - REDIS_USERNAME: z.string(), - REDIS_PASSWORD: z - .string() - .min(8, "Password is too short. Change your DB's password."), - REDIS_HOST: z.string(), - REDIS_PORT: z.coerce.number().int().min(1).max(9999), - SERVER_ID: z.string(), + GROK_API_KEY: z.string().min(1), + DISCORD_BOT_TOKEN: z.string().min(1), + NODE_ENV: z.enum(["development", "production", "test"]).default("development"), + LEVERET_PUB_KEY_B64: z.base64(), + REDIS_USERNAME: z.string(), + REDIS_PASSWORD: z.string().min(8, "Password is too short. Change your DB's password."), + REDIS_HOST: z.string(), + REDIS_PORT: z.coerce.number().int().min(1).max(9999), + SERVER_ID: z.string(), }); const RateLimitConfigSchema = z.object({ - maxStored: z.number().int().positive(), - incrementInterval_s: z.number().int().positive(), - incrementAmount: z.number().int().min(1), + maxStored: z.number().int().positive(), + incrementInterval_s: z.number().int().positive(), + incrementAmount: z.number().int().min(1), }); const EndpointConfigSchema = z.object({ - model: z.string(), - maxPromptTokens: z.number().int().min(10).max(9999), - maxContextTokens: z.number().int().max(9999), - maxTotalTokens: z.number().int().min(10).max(19999), - filters: z.string(), // Reference to filters config key - rateLimit: z.object({ - global: RateLimitConfigSchema, - user: RateLimitConfigSchema.extend({ - whitelist: z.array(z.string().min(18).max(19).optional()), + model: z.string(), + maxPromptTokens: z.number().int().min(10).max(9999), + maxContextTokens: z.number().int().max(9999), + maxTotalTokens: z.number().int().min(10).max(19999), + filters: z.string(), // Reference to filters config key + rateLimit: z.object({ + global: RateLimitConfigSchema, + user: RateLimitConfigSchema.extend({ + whitelist: z.array(z.string().min(18).max(19).optional()), + }), }), - }), }); export const ConfigFileSchema = z.object({ - port: z.number().int().min(1).max(9999), - grokTimeout_ms: z.number().int().min(1000), - runningIp: z.ipv4(), - loggerName: z.string(), - discordServerName: z.string(), - skipLeveretAuth: z.boolean(), - acceptedTags: z.array(z.string().optional()), - endpoints: z.record(z.string(), EndpointConfigSchema), + port: z.number().int().min(1).max(9999), + grokTimeout_ms: z.number().int().min(1000), + runningIp: z.ipv4(), + loggerName: z.string(), + discordServerName: z.string(), + skipLeveretAuth: z.boolean(), + acceptedTags: z.array(z.string().optional()), + endpoints: z.record(z.string(), EndpointConfigSchema), }); const endpointSpecificFiltersConfigSchema = z.object({ - enabledFilters: z.array(string()), - filterAction: z.enum(["warn", "replace", "block"]), - logViolations: z.boolean(), - customMessage: z.string(), + enabledFilters: z.array(string()), + filterAction: z.enum(["warn", "replace", "block"]), + logViolations: z.boolean(), + customMessage: z.string(), }); export const FiltersConfigFileSchema = z.object({ - grok: endpointSpecificFiltersConfigSchema, + grok: endpointSpecificFiltersConfigSchema, }); + +export type EnvSchema = z.infer; +export type ConfigSchema = z.infer; +export type FiltersSchema = z.infer; diff --git a/src/config/routes.ts b/src/config/routes.ts new file mode 100644 index 0000000..13cb998 --- /dev/null +++ b/src/config/routes.ts @@ -0,0 +1,11 @@ +export const AI_MODELS = ["grok", "premium-grok"] as const; +export type AiModel = (typeof AI_MODELS)[number]; + +export const SYSTEM_PROMPTS = ["default", "hogichan", "nomicord"] as const; +export type SystemPrompt = (typeof SYSTEM_PROMPTS)[number]; + +export const OREDIC_PACKS = ["nomi-ceu"] as const; +export type OredicPack = (typeof OREDIC_PACKS)[number]; + +export const OREDIC_ACTIONS = ["simplify", "match", "parse"] as const; +export type OredicAction = (typeof OREDIC_ACTIONS)[number]; diff --git a/src/config/schema.ts b/src/config/schema.ts index 9d12caa..bc28efd 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -1,48 +1,45 @@ -import z from "zod"; -import { - ConfigFileSchema, - EnvFileSchema, - FiltersConfigFileSchema, -} from "./fileSchema"; +import type z from "zod"; + +import { ConfigFileSchema, EnvFileSchema, FiltersConfigFileSchema } from "./fileSchema"; export const EnvSchema = EnvFileSchema; export const FilterConfigSchema = FiltersConfigFileSchema; export const ConfigSchema = ConfigFileSchema.transform((f) => ({ - PORT: f.port, - GROK_TIMEOUT: f.grokTimeout_ms, - RUNNING_IP: f.runningIp, - LOGGER_NAME: f.loggerName, - DISCORD_SERVER_NAME: f.discordServerName, - SKIP_LEVERET_AUTH: f.skipLeveretAuth, - ACCEPTED_TAGS: f.acceptedTags, - ENDPOINTS: Object.fromEntries( - Object.entries(f.endpoints).map(([key, endpoint]) => [ - key, - { - MODEL: endpoint.model, - MAX_PROMPT_TK: endpoint.maxPromptTokens, - MAX_CONTEXT_TK: endpoint.maxContextTokens, - MAX_TOTAL_TK: endpoint.maxTotalTokens, - FILTERS: endpoint.filters, - RATE_LIMIT: { - GLOBAL: { - MAX_STORED: endpoint.rateLimit.global.maxStored, - INTERVAL: endpoint.rateLimit.global.incrementInterval_s, - AMOUNT: endpoint.rateLimit.global.incrementAmount, - }, - USER: { - MAX_STORED: endpoint.rateLimit.user.maxStored, - INTERVAL: endpoint.rateLimit.user.incrementInterval_s, - AMOUNT: endpoint.rateLimit.user.incrementAmount, - WHITELIST: endpoint.rateLimit.user.whitelist.filter( - (id): id is string => Boolean(id) - ), - }, - }, - }, - ]) - ), + PORT: f.port, + GROK_TIMEOUT: f.grokTimeout_ms, + RUNNING_IP: f.runningIp, + LOGGER_NAME: f.loggerName, + DISCORD_SERVER_NAME: f.discordServerName, + SKIP_LEVERET_AUTH: f.skipLeveretAuth, + ACCEPTED_TAGS: f.acceptedTags, + ENDPOINTS: Object.fromEntries( + Object.entries(f.endpoints).map(([key, endpoint]) => [ + key, + { + MODEL: endpoint.model, + MAX_PROMPT_TK: endpoint.maxPromptTokens, + MAX_CONTEXT_TK: endpoint.maxContextTokens, + MAX_TOTAL_TK: endpoint.maxTotalTokens, + FILTERS: endpoint.filters, + RATE_LIMIT: { + GLOBAL: { + MAX_STORED: endpoint.rateLimit.global.maxStored, + INTERVAL: endpoint.rateLimit.global.incrementInterval_s, + AMOUNT: endpoint.rateLimit.global.incrementAmount, + }, + USER: { + MAX_STORED: endpoint.rateLimit.user.maxStored, + INTERVAL: endpoint.rateLimit.user.incrementInterval_s, + AMOUNT: endpoint.rateLimit.user.incrementAmount, + WHITELIST: endpoint.rateLimit.user.whitelist.filter((id): id is string => + Boolean(id), + ), + }, + }, + }, + ]), + ), })); export type Env = z.infer; diff --git a/src/config/validate.ts b/src/config/validate.ts index 080fab2..2b3aeef 100644 --- a/src/config/validate.ts +++ b/src/config/validate.ts @@ -1,42 +1,29 @@ -import { getLogger, Logger } from "../utils/Logger"; -import { - Config, - ConfigSchema, - Env, - EnvSchema, - FiltersConfig, - FilterConfigSchema, -} from "./schema"; +import type { Config, Env, FiltersConfig } from "./schema"; +import { ConfigSchema, EnvSchema, FilterConfigSchema } from "./schema"; export function validateEnvs(): Env { - const parsed = EnvSchema.safeParse(process.env); - if (!parsed.success) { - const errors = parsed.error.issues - .map((i) => `${i.path.join(".")}: ${i.message}`) - .join("; "); - throw new Error(`Env validation failed: ${errors}`); - } - return parsed.data; + const parsed = EnvSchema.safeParse(process.env); + if (!parsed.success) { + const errors = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "); + throw new Error(`Env validation failed: ${errors}`); + } + return parsed.data; } -export function validateConfigs(json: Object): Config { - const parsed = ConfigSchema.safeParse(json); - if (!parsed.success) { - const errors = parsed.error.issues - .map((i) => `${i.path.join(".")}: ${i.message}`) - .join("; "); - throw new Error(`Config validation failed: ${errors}`); - } - return parsed.data; +export function validateConfigs(json: object): Config { + const parsed = ConfigSchema.safeParse(json); + if (!parsed.success) { + const errors = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "); + throw new Error(`Config validation failed: ${errors}`); + } + return parsed.data; } -export function validateFiltersConfigs(json: Object): FiltersConfig { - const parsed = FilterConfigSchema.safeParse(json); - if (!parsed.success) { - const errors = parsed.error.issues - .map((i) => `${i.path.join(".")}: ${i.message}`) - .join("; "); - throw new Error(`Filter Config validation failed: ${errors}`); - } - return parsed.data; +export function validateFiltersConfigs(json: object): FiltersConfig { + const parsed = FilterConfigSchema.safeParse(json); + if (!parsed.success) { + const errors = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "); + throw new Error(`Filter Config validation failed: ${errors}`); + } + return parsed.data; } diff --git a/src/controllers/AiController.ts b/src/controllers/AiController.ts new file mode 100644 index 0000000..88062af --- /dev/null +++ b/src/controllers/AiController.ts @@ -0,0 +1,24 @@ +import type { NextFunction, Request, Response } from "express"; + +import { GrokService } from "../services/ai/GrokService"; + +export class AiController { + private service = new GrokService(); + + prompt = async (req: Request, res: Response, next: NextFunction) => { + const { model, systemPrompt } = req.ai || {}; + + if (!model || !systemPrompt) { + return next(new Error("AI model or system prompt is missing")); + } + + try { + const completion = await this.service.generateCompletion(req.body, model, systemPrompt); + + res.data = completion; + next(); + } catch (error) { + next(error); + } + }; +} diff --git a/src/controllers/GrokController.ts b/src/controllers/GrokController.ts deleted file mode 100644 index 2f00c4f..0000000 --- a/src/controllers/GrokController.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { NextFunction, Request, RequestHandler, Response } from "express"; -import { getGrokClient } from "../clients/GrokClient"; -import { - DEFAULT_PROMPT, - HOGICHAN_PROMPT, - NOMICORD_PROMPT, -} from "../loaders/storage"; -import { GrokInputData, SystemPromptChoice } from "../types/grok"; -import { formatCompletion } from "../utils/grok/formatter"; -import { getLogger } from "../utils/Logger"; -import { startTimer, stopTimer } from "../utils/Timer"; - -export class GrokController { - constructor() {} - - static systemPromptMappings = { - default: DEFAULT_PROMPT, - hogichan: HOGICHAN_PROMPT, - nomicord: NOMICORD_PROMPT, - }; - - async answerQuestionGeneric( - reqBody: GrokInputData, - model: string, - type: SystemPromptChoice, - endpointName: string - ) { - return await getGrokClient().client.chat.completions.create( - await formatCompletion(reqBody, model, type, endpointName) - ); - } - - handler = - (): RequestHandler => - async (req: Request, res: Response, next: NextFunction) => { - const reqBody = req.body; - const endpointConfig = req.endpointConfig?.config; - - if (!reqBody) { - next(new Error("Request body is missing")); - return; - } - - if (!endpointConfig) { - next(new Error("Endpoint configuration is missing")); - return; - } - - const model = endpointConfig.model; - const systemPrompt = endpointConfig.systemPrompt; - const endpointName = endpointConfig.endpointName; - - try { - startTimer("grok-req"); - const completion = await this.answerQuestionGeneric( - reqBody, - model, - systemPrompt, - endpointName - ); - - const time_taken_ms = stopTimer("grok-req").getTime(); - getLogger().simpleLog( - "info", - `Served Request on ${endpointConfig.endpointName} in ${time_taken_ms}ms` - ); - - res.json({ completion: completion, duration: time_taken_ms }); - } catch (err) { - next(err); - } - }; -} diff --git a/src/controllers/MembersController.ts b/src/controllers/MembersController.ts index 9e4579d..3e9756a 100644 --- a/src/controllers/MembersController.ts +++ b/src/controllers/MembersController.ts @@ -1,81 +1,37 @@ -import { RequestHandler, Request, Response, NextFunction } from "express"; -import { getDbHandler } from "../db/DbHandler"; -import { findDcUsernameById } from "../utils/bot/usernames"; -import { startTimer, stopTimer } from "../utils/Timer"; -import { getLogger } from "../utils/Logger"; +import type { NextFunction, Request, Response } from "express"; -export class MembersController { - dbHandler; - - constructor() { - this.dbHandler = getDbHandler(); - } - - async getIds() { - return await this.dbHandler.getHogMembers(); - } +import { MembersService } from "../services/members/MembersService"; - async getUsernames() { - const idsList = await this.dbHandler.getHogMembers(); - const usernamesList = await Promise.all( - idsList.map(async (memberId) => { - return await findDcUsernameById(memberId); - }) - ); - return usernamesList; - } - - async getUsernamesAndIds() { - const idsList = await this.dbHandler.getHogMembers(); - const idsToUsernames: Record = {}; - - await Promise.all( - idsList.map(async (element) => { - const username = await findDcUsernameById(element); - if (username) { - idsToUsernames[element] = username; - } else { - idsToUsernames[element] = "unknown"; +export class MembersController { + private service = new MembersService(); + + getIds = async (req: Request, res: Response, next: NextFunction) => { + try { + const members = await this.service.getIds(); + res.data = { ids: members }; + next(); + } catch (error) { + next(error); } - }) - ); - - return idsToUsernames; - } - - handler = - (): RequestHandler => - async (req: Request, res: Response, next: NextFunction) => { - const endpointConfig = req.endpointConfig; + }; - if (!endpointConfig) { - next(new Error("Endpoint configuration is missing")); - return; - } - const toFetch = endpointConfig.child; + getUsernames = async (req: Request, res: Response, next: NextFunction) => { + try { + const members = await this.service.getUsernames(); + res.data = { usernames: members }; + next(); + } catch (error) { + next(error); + } + }; - let members: any; - startTimer("members-fetch"); - try { - switch (toFetch) { - case "ids": - members = await this.getIds(); - break; - case "usernames": - members = await this.getUsernames(); - break; - case "usernames-and-ids": - members = await this.getUsernamesAndIds(); - break; + getUsers = async (req: Request, res: Response, next: NextFunction) => { + try { + const members = await this.service.getUsers(); + res.data = { users: members }; + next(); + } catch (error) { + next(error); } - const time_taken_ms = stopTimer("members-fetch").getTime(); - getLogger().simpleLog( - "info", - `Served Request on ${endpointConfig.main} in ${time_taken_ms}ms` - ); - res.json({ members: members, duration: time_taken_ms }); - } catch (err) { - next(err); - } }; } diff --git a/src/controllers/OredicController.ts b/src/controllers/OredicController.ts new file mode 100644 index 0000000..907b439 --- /dev/null +++ b/src/controllers/OredicController.ts @@ -0,0 +1,55 @@ +import type { NextFunction, Request, Response } from "express"; + +import type { OredicPack } from "../config/routes"; +import { OredicService } from "../services/oredic/OredicService"; +import { assertOredicRequest } from "../types/express"; +import { ErrorProne } from "../utils/parentClasses/ErrorProne"; + +export class OredicController extends ErrorProne { + constructor() { + super(); + } + + parse = async (req: Request, res: Response, next: NextFunction) => { + assertOredicRequest(req); + const service = this.initializeService(req.oredic.pack); + const filter = req.body.filter; + + if (!filter) { + this.setError( + 500, + "Received no filter in OredicController. Should have been validated properly first.", + ); + res.status(this.getErrorCode()).send(this.sendErrorToClient()); + return; + } + + const parsed = await service.parse(filter); + if (this.isError(parsed)) { + this.propagateError(parsed, "Could not parse", "OredicController"); + res.status(this.getErrorCode()).send(this.sendErrorToClient()); + return; + } + + res.data = { ast: parsed }; + }; + + build = async (req: Request, res: Response, next: NextFunction) => { + assertOredicRequest(req); + const service = this.initializeService(req.oredic.pack); + }; + + match = async (req: Request, res: Response, next: NextFunction) => { + assertOredicRequest(req); + const service = this.initializeService(req.oredic.pack); + }; + + simplify = async (req: Request, res: Response, next: NextFunction) => { + assertOredicRequest(req); + const service = this.initializeService(req.oredic.pack); + }; + + initializeService(pack: OredicPack) { + return new OredicService(pack); + } +} diff --git a/src/core/Core.ts b/src/core/Core.ts new file mode 100644 index 0000000..455f024 --- /dev/null +++ b/src/core/Core.ts @@ -0,0 +1,37 @@ +import { Logger } from "./logs/Logger"; +import { Timer } from "./Timer"; + +class Core { + private _logger?: Logger; + private _timers = new Map(); + + get logger(): Logger { + return (this._logger ??= new Logger()); + } + + startTimer(id: string): void { + if (!this._timers.get(id)) { + this._timers.set(id, new Timer()); + } + } + + stopTimer(id: string): Timer { + const timer = this._timers.get(id); + if (!timer) throw new Error(`Timer ${id} not found`); + this._timers.delete(id); + return timer; + } + + queryTimer(id: string): Timer { + const timer = this._timers.get(id); + if (!timer) throw new Error(`Timer ${id} not found`); + return timer; + } + + reset(): void { + this._logger = undefined; + this._timers.clear(); + } +} + +export const core = new Core(); diff --git a/src/core/Dependencies.ts b/src/core/Dependencies.ts new file mode 100644 index 0000000..3d5ad57 --- /dev/null +++ b/src/core/Dependencies.ts @@ -0,0 +1,7 @@ +class Dependencies { + constructor() {} + + resetAll(): void {} +} + +export const dependencies = new Dependencies(); diff --git a/src/core/Timer.ts b/src/core/Timer.ts new file mode 100644 index 0000000..1ad54b6 --- /dev/null +++ b/src/core/Timer.ts @@ -0,0 +1,52 @@ +import type { TimerResult, TimeUnit } from "../types/time/timer"; + +interface UnitConfig { + label: string; + factor: number; // Multiply raw ms by this to get the unit + threshold: number; // For auto-selection (in ms) +} + +export class Timer { + startTime: number; + + constructor() { + this.startTime = performance.now(); + } + + /** + * Get the elapsed time since the timer started. + * + * @param unit (default = auto) The time unit to use for the adjusted time. + * @param precision (default = 2) Number of decimal places to include in the formatted time. + * + * @returns An object containing three representations of the elapsed time: + * - `raw`: The raw elapsed time in milliseconds (always in ms regardless of unit parameter) + * - `adjusted`: The elapsed time converted to the specified unit + * - `formatted`: A human-readable string with the adjusted time and unit label (e.g., "123.45ms") + */ + getTime(unit: TimeUnit | "auto" = "auto", precision: number = 2): TimerResult { + const raw = performance.now() - this.startTime; + const selectedUnit = unit === "auto" ? this.selectUnit(raw) : unit; + const appConfig = Timer.UNITS[selectedUnit]; + + const adjusted = raw * appConfig.factor; + + const formatted = `${adjusted.toFixed(precision)}${appConfig.label}`; + + return { raw, adjusted, formatted }; + } + + private static readonly UNITS: Record = { + micro: { label: "μs", factor: 1000, threshold: 1 }, + ms: { label: "ms", factor: 1, threshold: 1000 }, + s: { label: "s", factor: 1 / 1000, threshold: 60000 }, + m: { label: "m", factor: 1 / 60000, threshold: -1 }, + }; + + private selectUnit(timeMs: number): TimeUnit { + if (timeMs < Timer.UNITS.micro.threshold) return "micro"; + if (timeMs < Timer.UNITS.ms.threshold) return "ms"; + if (timeMs < Timer.UNITS.s.threshold) return "s"; + return "m"; + } +} diff --git a/src/db/DbHandler.ts b/src/db/DbHandler.ts index 324f6a6..a0fee97 100644 --- a/src/db/DbHandler.ts +++ b/src/db/DbHandler.ts @@ -1,181 +1,149 @@ import { getRedisClient } from "../clients/RedisClient"; import { config } from "../config/config"; +import { getLogger } from "../helpers/Logger"; import { findDcUsernameById } from "../utils/bot/usernames"; -import { getLogger } from "../utils/Logger"; let dbHandler: DbHandler | null = null; export class DbHandler { - client; - - constructor() { - this.client = getRedisClient().client; - } - - async init() { - await this.createGlobalRatesForAllEndpoints(); - } - - async createGlobalRatesForAllEndpoints() { - for (const [endpointName, endpointConfig] of Object.entries( - config.ENDPOINTS - )) { - await this.client.set( - `${endpointName}:GlobalRates`, - endpointConfig.RATE_LIMIT.GLOBAL.MAX_STORED - ); - getLogger().simpleLog( - "success", - `Global Rates Created for ${endpointName}` - ); + client; + + constructor() { + this.client = getRedisClient().client; + } + + async init() { + await this.createGlobalRatesForAllEndpoints(); } - } - - async createUser(discordId: string, endpointName: string, maxStored: number) { - const username = await findDcUsernameById(discordId); - if (!username) { - getLogger().simpleLog( - "warn", - `Could not find discord username for ${discordId}` - ); - return false; + + async createGlobalRatesForAllEndpoints() { + for (const [endpointName, endpointConfig] of Object.entries(config.ENDPOINTS)) { + await this.client.set(`${endpointName}:GlobalRates`, endpointConfig.RATE_LIMIT.GLOBAL.MAX_STORED); + getLogger().simpleLog("success", `Global Rates Created for ${endpointName}`); + } } - await this.client.hSet(`${endpointName}:user:${discordId}`, { - username: username, - rates: maxStored, - }); - return true; - } - - async getGlobalRates(endpointName: string) { - const rates = await this.client.get(`${endpointName}:GlobalRates`); - if (!rates) { - getLogger().simpleLog( - "warn", - `Error Fetching Global Rates for ${endpointName}` - ); - return null; + + async createUser(discordId: string, endpointName: string, maxStored: number) { + const username = await findDcUsernameById(discordId); + if (!username) { + getLogger().simpleLog("warn", `Could not find discord username for ${discordId}`); + return false; + } + await this.client.hSet(`${endpointName}:user:${discordId}`, { + username: username, + rates: maxStored, + }); + return true; + } + + async getGlobalRates(endpointName: string) { + const rates = await this.client.get(`${endpointName}:GlobalRates`); + if (!rates) { + getLogger().simpleLog("warn", `Error Fetching Global Rates for ${endpointName}`); + return null; + } + return parseInt(rates); } - return parseInt(rates); - } - async getUsername(discordId: string, endpointName: string) { - const userData = await this.getUserData(discordId, endpointName); - if (!userData.username) { - throw new Error(`No username found for Discord ID: ${discordId}`); + async getUsername(discordId: string, endpointName: string) { + const userData = await this.getUserData(discordId, endpointName); + if (!userData.username) { + throw new Error(`No username found for Discord ID: ${discordId}`); + } + return userData.username; } - return userData.username; - } - - async getUserRates(discordId: string, endpointName: string) { - const userData = await this.getUserData(discordId, endpointName); - if (!userData.rates) { - throw new Error( - `No rates found for Discord ID: ${discordId} on ${endpointName}` - ); + + async getUserRates(discordId: string, endpointName: string) { + const userData = await this.getUserData(discordId, endpointName); + if (!userData.rates) { + throw new Error(`No rates found for Discord ID: ${discordId} on ${endpointName}`); + } + return parseInt(userData.rates); } - return parseInt(userData.rates); - } - - async updateGlobalRates(endpointName: string, type: "give" | "take") { - const rates = await this.getGlobalRates(endpointName); - if (rates === null) return; - const globalConfig = config.ENDPOINTS[endpointName].RATE_LIMIT.GLOBAL; - let newRates: number; - let tmpRates: number; - - switch (type) { - case "give": - tmpRates = rates + globalConfig.AMOUNT; - newRates = - tmpRates >= globalConfig.MAX_STORED - ? globalConfig.MAX_STORED - : tmpRates; - break; - case "take": - tmpRates = rates - 1; - newRates = tmpRates <= 0 ? 0 : tmpRates; - break; + + async updateGlobalRates(endpointName: string, type: "give" | "take") { + const rates = await this.getGlobalRates(endpointName); + if (rates === null) return; + const globalConfig = config.ENDPOINTS[endpointName].RATE_LIMIT.GLOBAL; + let newRates: number; + let tmpRates: number; + + switch (type) { + case "give": + tmpRates = rates + globalConfig.AMOUNT; + newRates = tmpRates >= globalConfig.MAX_STORED ? globalConfig.MAX_STORED : tmpRates; + break; + case "take": + tmpRates = rates - 1; + newRates = tmpRates <= 0 ? 0 : tmpRates; + break; + } + + await this.client.set(`${endpointName}:GlobalRates`, newRates); } - await this.client.set(`${endpointName}:GlobalRates`, newRates); - } - - async updateUserRates( - discordId: string, - endpointName: string, - type: "give" | "take" - ) { - const rates = await this.getUserRates(discordId, endpointName); - const userConfig = config.ENDPOINTS[endpointName].RATE_LIMIT.USER; - let newRates: number; - let tmpRates: number; - - switch (type) { - case "give": - tmpRates = rates + userConfig.AMOUNT; - newRates = - tmpRates >= userConfig.MAX_STORED ? userConfig.MAX_STORED : tmpRates; - break; - case "take": - tmpRates = rates - 1; - newRates = tmpRates <= 0 ? 0 : tmpRates; - break; + async updateUserRates(discordId: string, endpointName: string, type: "give" | "take") { + const rates = await this.getUserRates(discordId, endpointName); + const userConfig = config.ENDPOINTS[endpointName].RATE_LIMIT.USER; + let newRates: number; + let tmpRates: number; + + switch (type) { + case "give": + tmpRates = rates + userConfig.AMOUNT; + newRates = tmpRates >= userConfig.MAX_STORED ? userConfig.MAX_STORED : tmpRates; + break; + case "take": + tmpRates = rates - 1; + newRates = tmpRates <= 0 ? 0 : tmpRates; + break; + } + + await this.client.hSet(`${endpointName}:user:${discordId}`, "rates", newRates); } - await this.client.hSet( - `${endpointName}:user:${discordId}`, - "rates", - newRates - ); - } - - async getUserData(discordId: string, endpointName: string) { - const key = `${endpointName}:user:${discordId}`; - const exists = !!(await this.client.exists(key)); - if (!exists) { - const maxStored = - config.ENDPOINTS[endpointName].RATE_LIMIT.USER.MAX_STORED; - const created = await this.createUser(discordId, endpointName, maxStored); - if (!created) { - throw new Error( - `Failed to create user for Discord ID: ${discordId} on ${endpointName}` - ); - } + async getUserData(discordId: string, endpointName: string) { + const key = `${endpointName}:user:${discordId}`; + const exists = Boolean(await this.client.exists(key)); + if (!exists) { + const maxStored = config.ENDPOINTS[endpointName].RATE_LIMIT.USER.MAX_STORED; + const created = await this.createUser(discordId, endpointName, maxStored); + if (!created) { + throw new Error(`Failed to create user for Discord ID: ${discordId} on ${endpointName}`); + } + } + return this.client.hGetAll(key); } - return await this.client.hGetAll(key); - } - async getAllUserKeysForEndpoint(endpointName: string) { - const pattern = `${endpointName}:user:*`; - return await this.client.keys(pattern); - } + async getAllUserKeysForEndpoint(endpointName: string) { + const pattern = `${endpointName}:user:*`; + return this.client.keys(pattern); + } - async createHog(discordIds: string[]) { - await this.client.sAdd("members", discordIds); - } + async createHog(discordIds: string[]) { + await this.client.sAdd("members", discordIds); + } - async getHogMembers() { - return await this.client.sMembers("members"); - } + async getHogMembers() { + return this.client.sMembers("members"); + } - async addToHog(discordId: string) { - await this.client.sAdd("members", discordId); - } + async addToHog(discordId: string) { + await this.client.sAdd("members", discordId); + } - async removeFromHog(discordId: string) { - await this.client.sRem("members", discordId); - } + async removeFromHog(discordId: string) { + await this.client.sRem("members", discordId); + } } export function initDbHandler(): DbHandler { - if (dbHandler) return dbHandler; - dbHandler = new DbHandler(); - return dbHandler; + if (dbHandler) return dbHandler; + dbHandler = new DbHandler(); + return dbHandler; } export function getDbHandler(): DbHandler { - if (!dbHandler) - throw new Error("Db Handler not initialized. Call initDbHandler() first."); - return dbHandler; + if (!dbHandler) throw new Error("Db Handler not initialized. Call initDbHandler() first."); + return dbHandler; } diff --git a/src/discord.ts b/src/discord.ts deleted file mode 100644 index b2c2696..0000000 --- a/src/discord.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BotEventHandler } from "./bot/EventHandler"; -import { initDiscordClient, getDiscordClient } from "./clients/DiscordClient"; - -export async function initDiscord() { - initDiscordClient(); - const client = getDiscordClient().client; - const eventHandler = new BotEventHandler(); - - client.once("clientReady", () => eventHandler.onReady()); - client.on("guildMemberAdd", (member) => eventHandler.onNewMember(member)); - client.on("guildMemberRemove", (member) => - eventHandler.onRemoveMember(member) - ); - - await getDiscordClient().connect(); -} diff --git a/src/express.ts b/src/express.ts deleted file mode 100644 index e03d491..0000000 --- a/src/express.ts +++ /dev/null @@ -1,34 +0,0 @@ -import express from "express"; -import { getLogger } from "./utils/Logger"; -import { routes } from "./routes"; - -export function initExpress() { - const app = express(); - - app.use(routes()); - - app.use( - ( - err: Error, - req: express.Request, - res: express.Response, - next: express.NextFunction - ) => { - getLogger().simpleLog("error", `Unhandled error: ${err.message}`); - console.error(err.stack); - - if (!res.headersSent) { - res.status(500).json({ - error: "Internal Server Error", - message: - process.env.NODE_ENV === "development" - ? err.message - : "Something went wrong", - }); - } - } - ); - - getLogger().simpleLog("success", "Express App Initialized Successfully"); - return app; -} diff --git a/src/helpers/Logger.ts b/src/helpers/Logger.ts new file mode 100644 index 0000000..2ba4b3c --- /dev/null +++ b/src/helpers/Logger.ts @@ -0,0 +1,173 @@ +import { config } from "../config/config"; +import type { LogType } from "../types/server"; +import { queryTimer } from "./Timer"; + +let logger: Logger | null = null; + +enum AnsiColor { + ERROR = "\x1b[31m", + SUCCESS = "\x1b[32m", + WARN = "\x1b[33m", + TELEMETRY = "\x1b[34m", + DEBUG = "\x1b[35m", + INFO = "\x1b[36m", + FORMAT = "\x1b[37m", + RESET = "\x1b[0m", +} + +enum FormattingConstant { + MAX_TYPE_LENGTH = 9, // "success".length + PROGRESS_BAR_FILLED = "█", + PROGRESS_BAR_EMPTY = "░", + FORMATTING_DASH = "-", + FORMATTING_TARGET_WIDTH = 50, + SECONDS_PER_MINUTE = 60, + MILLISECONDS_PER_SECOND = 1000, +} + +export class Logger { + private name: string; + + constructor() { + this.name = config.LOGGER_NAME; + } + + simpleLog(type: LogType, message: string): void { + const timestamp = this.getCurrentTimestamp(); + const logPrefix = this.formatLogPrefix(type, timestamp); + const coloredMessage = this.colorize(message, type); + + console.log(`${logPrefix}: ${coloredMessage}`); + } + + formattingLog(title: string): void { + const timestamp = this.getCurrentTimestamp(); + const logPrefix = this.formatLogPrefix("format", timestamp); + const formattedTitle = this.formatTitle(title); + const coloredTitle = this.colorize(formattedTitle, "format"); + + console.log(`${logPrefix}: ${coloredTitle}`); + } + + progressBar( + currentCount: number, + totalCount: number, + timerId: string, + barWidth: number = 50, + label: string = "", + ): void { + const progress = this.buildProgressBar(currentCount, totalCount, timerId, barWidth, label); + + const coloredProgress = this.colorize(progress, "info"); + process.stdout.write(`\r${coloredProgress}`); + + const isComplete = currentCount === totalCount; + if (isComplete) { + process.stdout.write("\n"); + } + } + + /* + * Formatting helpers + */ + + private formatLogPrefix(type: LogType | "format", timestamp: string): string { + const logName = `[${this.name}:${type.toUpperCase()}]`; + const logTimestamp = `[${timestamp}]`; + const padding = " ".repeat(FormattingConstant.MAX_TYPE_LENGTH - type.length); + + return `${logName}${padding}@${logTimestamp}`; + } + + private formatTitle(title: string): string { + const dashCount = (FormattingConstant.FORMATTING_TARGET_WIDTH - title.length) / 2; + const dashes = (FormattingConstant.FORMATTING_DASH as string).repeat(dashCount); + const hasOddLength = dashCount % 1 !== 0; + const extraDash = hasOddLength ? FormattingConstant.FORMATTING_DASH : ""; + + return `|${dashes} ${title} ${dashes}${extraDash}|`; + } + + private buildProgressBar( + currentCount: number, + totalCount: number, + timerId: string, + barWidth: number, + label: string, + ): string { + const percentage = Math.floor((currentCount / totalCount) * 100); + const bar = this.createBar(currentCount, totalCount, barWidth); + const timeInfo = this.createTimeInfo(currentCount, totalCount, timerId); + const labelText = label ? ` ${label}` : ""; + + return `[${bar}] ${percentage}% (${currentCount}/${totalCount})${labelText} ${timeInfo}`; + } + + private createBar(currentCount: number, totalCount: number, barWidth: number): string { + const filledWidth = Math.floor((currentCount / totalCount) * barWidth); + const emptyWidth = Math.max(0, barWidth - filledWidth); + + const filled = (FormattingConstant.PROGRESS_BAR_FILLED as string).repeat(filledWidth); + const empty = (FormattingConstant.PROGRESS_BAR_EMPTY as string).repeat(emptyWidth); + + return `${filled}${empty}`; + } + + private createTimeInfo(currentCount: number, totalCount: number, timerId: string): string { + const elapsedMilliseconds = queryTimer(timerId).getTime("ms").raw; + const elapsedSeconds = Math.floor(elapsedMilliseconds / FormattingConstant.MILLISECONDS_PER_SECOND); + const elapsedFormatted = this.formatTime(elapsedSeconds); + + let etaText = ""; + const isInProgress = currentCount < totalCount && currentCount > 0; + + if (isInProgress) { + const remainingMilliseconds = (elapsedMilliseconds / currentCount) * (totalCount - currentCount); + const remainingSeconds = Math.floor( + remainingMilliseconds / FormattingConstant.MILLISECONDS_PER_SECOND, + ); + const etaFormatted = this.formatTime(remainingSeconds); + etaText = ` ETA: ${etaFormatted}`; + } + + return `[${elapsedFormatted}]${etaText}`; + } + + private formatTime(totalSeconds: number): string { + const minutes = Math.floor(totalSeconds / FormattingConstant.SECONDS_PER_MINUTE); + const seconds = totalSeconds % FormattingConstant.SECONDS_PER_MINUTE; + const paddedSeconds = seconds.toString().padStart(2, "0"); + + return `${minutes}:${paddedSeconds}`; + } + + private colorize(text: string, type: LogType | "format"): string { + const colorMap: Record = { + success: AnsiColor.SUCCESS, + info: AnsiColor.INFO, + warn: AnsiColor.WARN, + error: AnsiColor.ERROR, + debug: AnsiColor.DEBUG, + telemetry: AnsiColor.TELEMETRY, + format: AnsiColor.FORMAT, + }; + + const color = colorMap[type]; + return `${color}${text}${AnsiColor.RESET}`; + } + + private getCurrentTimestamp(): string { + return new Date().toISOString(); + } +} + +export function initLogger(): Logger { + if (logger) return logger; + logger = new Logger(); + return logger; +} + +export function getLogger(): Logger { + if (!logger) throw new Error("Logger not initialized. Call initLogger() first."); + return logger; +} diff --git a/src/helpers/Timer.ts b/src/helpers/Timer.ts new file mode 100644 index 0000000..1ac9dcb --- /dev/null +++ b/src/helpers/Timer.ts @@ -0,0 +1,84 @@ +import type { TimerRes } from "../types/timer"; + +const timers: Map = new Map(); + +export class Timer { + startTime: number; + + constructor() { + this.startTime = performance.now(); + } + + getTime(unit: "micro" | "ms" | "s" | "m" | "auto" = "auto", precision: number = 2): TimerRes { + const timeTaken_ms = performance.now() - this.startTime; + let adjustedTime: number = 0; + let unitLabel: string = ""; + + switch (unit) { + case "auto": + if (timeTaken_ms < 1) { + adjustedTime = timeTaken_ms * 1000; + unitLabel = "μs"; + } else if (timeTaken_ms < 1000) { + adjustedTime = timeTaken_ms; + unitLabel = "ms"; + } else if (timeTaken_ms < 60000) { + adjustedTime = timeTaken_ms / 1000; + unitLabel = "s"; + } else { + adjustedTime = timeTaken_ms / 60000; + unitLabel = "m"; + } + break; + + case "micro": + adjustedTime = timeTaken_ms * 1000; + unitLabel = "μs"; + break; + + case "ms": + adjustedTime = timeTaken_ms; + unitLabel = "ms"; + break; + + case "s": + adjustedTime = timeTaken_ms / 1000; + unitLabel = "s"; + break; + + case "m": + adjustedTime = timeTaken_ms / 60000; + unitLabel = "m"; + break; + } + + const formattedTime = `${adjustedTime.toFixed(precision)}${unitLabel}`; + + return { + raw: timeTaken_ms, + adjusted: adjustedTime, + formatted: formattedTime, + }; + } +} + +export function startTimer(id: string): void { + if (!timers.get(id)) timers.set(id, new Timer()); +} + +export function stopTimer(id: string): Timer { + const time = timers.get(id); + if (!time) throw new Error(`Timer ${id} not initialized. Call startTimer(id:string) first.`); + timers.delete(id); + return time; +} + +export function queryTimer(id: string): Timer { + const time = timers.get(id); + if (!time) throw new Error(`Timer ${id} not initialized. Call startTimer(id:string) first.`); + return time; +} + +export function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/loaders/files.ts b/src/loaders/files.ts index 793a387..df9630c 100644 --- a/src/loaders/files.ts +++ b/src/loaders/files.ts @@ -1,5 +1,5 @@ -import path from "path"; import fs from "fs"; +import path from "path"; const STORAGE_DIR = path.join(process.cwd(), "storage"); const CONFIG_DIR = path.join(STORAGE_DIR, "config"); @@ -13,10 +13,14 @@ export const HOGICHAN_PROMPT_FILE = path.join(PROMPTS_DIR, "hogichan.txt"); export const NOMICORD_PROMPT_FILE = path.join(PROMPTS_DIR, "nomicord.txt"); export const FILTERS_FILES_DIR = path.join(STORAGE_DIR, "filters"); -export let FILTERS_FILES = new Map(); +export const FILTERS_FILES = new Map(); fs.readdirSync(FILTERS_FILES_DIR).forEach((file) => - FILTERS_FILES.set( - file.replace(".json", ""), - path.join(FILTERS_FILES_DIR, file) - ) + FILTERS_FILES.set(file.replace(".json", ""), path.join(FILTERS_FILES_DIR, file)), +); + +export const OREDIC_DIR = path.join(STORAGE_DIR, "oredic"); +export const DUMPS_DIR = path.join(OREDIC_DIR, "dumps"); +export const DUMPS_FILES = new Map(); +fs.readdirSync(DUMPS_DIR).forEach((file) => + DUMPS_FILES.set(file.replace(".txt", ""), path.join(DUMPS_DIR, file)), ); diff --git a/src/loaders/filters.ts b/src/loaders/filters.ts index 7c7176e..f895933 100644 --- a/src/loaders/filters.ts +++ b/src/loaders/filters.ts @@ -1,22 +1,23 @@ import fs from "fs"; -import { Filter, PatternConfig } from "../types/server"; -import { FILTERS_FILES } from "./files"; + import { filtersConfig } from "../config/config"; +import type { Filter, PatternConfig } from "../types/server"; +import { FILTERS_FILES } from "./files"; -export let FILTERS: Filter[] = []; +export const FILTERS: Filter[] = []; FILTERS_FILES.forEach((value, key) => { - if (!filtersConfig.grok.enabledFilters.includes(key)) { - return; - } + if (!filtersConfig.grok.enabledFilters.includes(key)) { + return; + } - const object = JSON.parse(fs.readFileSync(value, "utf-8")); + const object = JSON.parse(fs.readFileSync(value, "utf-8")); - object.patterns.forEach((element: PatternConfig) => { - const filter: Filter = { - pattern: new RegExp(`${element.pattern}`, "gi"), - severity: element.severity, - description: element.description, - }; - FILTERS.push(filter); - }); + object.patterns.forEach((element: PatternConfig) => { + const filter: Filter = { + pattern: new RegExp(`${element.pattern}`, "gi"), + severity: element.severity, + description: element.description, + }; + FILTERS.push(filter); + }); }); diff --git a/src/loaders/keys.ts b/src/loaders/keys.ts index 2a3c108..fa074bd 100644 --- a/src/loaders/keys.ts +++ b/src/loaders/keys.ts @@ -1,8 +1,9 @@ import { createPublicKey } from "crypto"; + import { env } from "../config/config"; export const LEVERET_PUBLIC_KEY = createPublicKey({ - key: Buffer.from(env.LEVERET_PUB_KEY_B64, "base64"), - format: "der", - type: "spki", + key: Buffer.from(env.LEVERET_PUB_KEY_B64, "base64"), + format: "der", + type: "spki", }); diff --git a/src/loaders/storage.ts b/src/loaders/storage.ts index 755c491..e4124a3 100644 --- a/src/loaders/storage.ts +++ b/src/loaders/storage.ts @@ -1,19 +1,22 @@ import fs from "fs"; + import { - CONFIG_FILE, - DEFAULT_PROMPT_FILE, - FILTERS_CONFIG_FILE, - HOGICHAN_PROMPT_FILE, - NOMICORD_PROMPT_FILE, + CONFIG_FILE, + DEFAULT_PROMPT_FILE, + DUMPS_FILES, + FILTERS_CONFIG_FILE, + HOGICHAN_PROMPT_FILE, + NOMICORD_PROMPT_FILE, } from "./files"; export const RAW_CONFIG = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8")); -export const RAW_FILTERS_CONFIG = JSON.parse( - fs.readFileSync(FILTERS_CONFIG_FILE, "utf-8") -); +export const RAW_FILTERS_CONFIG = JSON.parse(fs.readFileSync(FILTERS_CONFIG_FILE, "utf-8")); export const DEFAULT_PROMPT = fs.readFileSync(DEFAULT_PROMPT_FILE, "utf-8"); -export const HOGICHAN_PROMPT = - DEFAULT_PROMPT + "\n" + fs.readFileSync(HOGICHAN_PROMPT_FILE, "utf-8"); -export const NOMICORD_PROMPT = - DEFAULT_PROMPT + "\n" + fs.readFileSync(NOMICORD_PROMPT_FILE, "utf-8"); +export const HOGICHAN_PROMPT = DEFAULT_PROMPT + "\n" + fs.readFileSync(HOGICHAN_PROMPT_FILE, "utf-8"); +export const NOMICORD_PROMPT = DEFAULT_PROMPT + "\n" + fs.readFileSync(NOMICORD_PROMPT_FILE, "utf-8"); + +export const DUMPS = new Map(); +DUMPS_FILES.forEach((file, fileName) => { + DUMPS.set(fileName, fs.readFileSync(file, "utf-8")); +}); diff --git a/src/middleware/analytics/perf/timer.ts b/src/middleware/analytics/perf/timer.ts new file mode 100644 index 0000000..3e02d24 --- /dev/null +++ b/src/middleware/analytics/perf/timer.ts @@ -0,0 +1,21 @@ +import type { NextFunction, Request, Response } from "express"; + +import { getLogger } from "../../../helpers/Logger"; +import { startTimer, stopTimer } from "../../../helpers/Timer"; + +export const timerStart = (req: Request, res: Response, next: NextFunction) => { + req.timerId = `req-${Date.now()}`; + startTimer(req.timerId); + + next(); +}; + +export const timerStop = (req: Request, res: Response, next: NextFunction) => { + const timing = stopTimer(req.timerId).getTime("ms", 0); + + const endpoint = req.endpoint || "unknown"; + getLogger().simpleLog("telemetry", `Served ${endpoint} in ${timing.formatted}`); + + res.timing = timing; + next(); +}; diff --git a/src/middleware/auth/globalRateLimits.ts b/src/middleware/auth/globalRateLimits.ts new file mode 100644 index 0000000..4cf9730 --- /dev/null +++ b/src/middleware/auth/globalRateLimits.ts @@ -0,0 +1,36 @@ +import type { NextFunction, Request, Response } from "express"; + +import { config } from "../../config/config"; +import { getDbHandler } from "../../db/DbHandler"; +import { getLogger } from "../../helpers/Logger"; + +export const globalRateLimits = async (req: Request, res: Response, next: NextFunction): Promise => { + const aiModel = req.ai?.model; + + if (!aiModel) { + res.status(500).json({ error: "AI model not found" }); + return; + } + + const modelConfig = config.ENDPOINTS[aiModel]; + if (!modelConfig) { + res.status(500).json({ error: "Model configuration not found" }); + return; + } + + const isAllowed = await checkRateLimits(req.body.userId, aiModel, modelConfig.RATE_LIMIT.USER.WHITELIST); + if (!isAllowed) { + getLogger().simpleLog("warn", `Global Rate Limit Reached for ${aiModel}`); + res.status(429).json({ error: "You're Being Rate Limited by the Global Rate Limit" }); + return; + } + await getDbHandler().updateGlobalRates(aiModel, "take"); + next(); +}; + +async function checkRateLimits(dcUserId: string, modelName: string, whitelist: string[]) { + if (whitelist.includes(dcUserId)) return true; + const rates = await getDbHandler().getGlobalRates(modelName); + if (rates === null) return false; + return rates > 0; +} diff --git a/src/middleware/auth/leveretAuth.ts b/src/middleware/auth/leveretAuth.ts new file mode 100644 index 0000000..f03c88d --- /dev/null +++ b/src/middleware/auth/leveretAuth.ts @@ -0,0 +1,67 @@ +import { createHash, verify } from "crypto"; +import type { NextFunction, Request, Response } from "express"; + +import { config } from "../../config/config"; +import { getLogger } from "../../helpers/Logger"; +import { LEVERET_PUBLIC_KEY } from "../../loaders/keys"; + +export const leveretAuth = (req: Request, res: Response, next: NextFunction): void => { + const authResult = authRequest(req); + + if (!authResult) { + getLogger().simpleLog("warn", "Unauthorized Request For Leveret"); + res.status(401).json({ error: "Unauthorized" }); + return; + } + getLogger().simpleLog("telemetry", "Authorized Leveret Request"); + req.flags = { isLeveret: true }; + next(); +}; + +const HEADERS = { + signature: "x-leveret-signature-ed25519", + tag: "x-leveret-tag", + timestamp: "x-leveret-timestamp", + requestId: "x-leveret-request-id", +}; + +function authRequest(req: any) { + if (config.SKIP_LEVERET_AUTH) return true; + + // Mostly from https://gist.github.com/NotMyWing/632d738644c17aa71931169af5cb2767 + const headers = req.headers ?? {}; + const signatureB64 = headers[HEADERS.signature]; + const tagName = headers[HEADERS.tag]; + const timestamp = headers[HEADERS.timestamp]; + const requestId = headers[HEADERS.requestId]; + + // Do less expensive checks first to save on potential compute + if (!signatureB64 || !tagName) { + getLogger().simpleLog("warn", "LEVE_AUTH: Could not find signatureB64 nor tagName"); + return false; + } + if (!config.ACCEPTED_TAGS.includes(tagName)) { + getLogger().simpleLog("warn", "LEVE_AUTH: Could not find tagName in the accepted tags"); + return false; + } + + const method = String(req.method ?? "GET").toUpperCase(); + const host = req.get?.("host") ?? headers.host; + if (!host) { + getLogger().simpleLog("warn", "LEVE_AUTH: Could not find host"); + return false; + } + + const url = `${req.protocol ?? "https"}://${host}${req.originalUrl ?? req.url ?? ""}`; + const bodyHash = createHash("sha256").update(req.rawBody).digest("hex"); + const canonical = [timestamp, requestId, method, url, bodyHash].join("\n"); + + const signature = Buffer.from(signatureB64, "base64"); + const valid = verify(null, Buffer.from(canonical), LEVERET_PUBLIC_KEY, signature); + if (!valid) { + getLogger().simpleLog("warn", "LEVE_AUTH: Invalid signature"); + return false; + } + + return true; +} diff --git a/src/middleware/auth/userRateLimits.ts b/src/middleware/auth/userRateLimits.ts new file mode 100644 index 0000000..887b0f7 --- /dev/null +++ b/src/middleware/auth/userRateLimits.ts @@ -0,0 +1,38 @@ +import type { NextFunction, Request, Response } from "express"; + +import { config } from "../../config/config"; +import { getDbHandler } from "../../db/DbHandler"; +import { getLogger } from "../../helpers/Logger"; + +export const userRateLimits = async (req: Request, res: Response, next: NextFunction): Promise => { + const userId = req.body.userId; + const aiModel = req.ai?.model; + + if (!aiModel) { + res.status(500).json({ error: "AI model not found" }); + return; + } + + const modelConfig = config.ENDPOINTS[aiModel]; + if (!modelConfig) { + res.status(500).json({ error: "Model configuration not found" }); + return; + } + + const isAllowed = await checkRateLimits(userId, aiModel, modelConfig.RATE_LIMIT.USER.WHITELIST); + + if (!isAllowed) { + const username = await getDbHandler().getUsername(userId, aiModel); + getLogger().simpleLog("warn", `User: ${username} got rate limited on ${aiModel}`); + res.status(429).json({ error: "You're Being Rate Limited" }); + return; + } + + next(); +}; + +async function checkRateLimits(dcUserId: string, modelName: string, whitelist: string[]) { + if (whitelist.includes(dcUserId)) return true; + const rates = await getDbHandler().getUserRates(dcUserId, modelName); + return rates > 0; +} diff --git a/src/middleware/cleanup.ts b/src/middleware/cleanup.ts deleted file mode 100644 index d2b1421..0000000 --- a/src/middleware/cleanup.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { NextFunction, Response, Request } from "express"; -import { getLogger } from "../utils/Logger"; -import { getDbHandler } from "../db/DbHandler"; - -export const cleanup = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - const userId = req.body.userId; - const endpointConfig = req.endpointConfig?.config; - - if (!endpointConfig) { - res.status(500).json({ error: "Endpoint configuration not found" }); - return; - } - - if (req.endpointConfig?.source === "leveret") { - await getDbHandler().updateGlobalRates(endpointConfig.endpointName, "take"); - await getDbHandler().updateUserRates( - userId, - endpointConfig.endpointName, - "take" - ); - } - - getLogger().simpleLog("info", "Authorized Request"); - next(); -}; diff --git a/src/middleware/endpointData.ts b/src/middleware/endpointData.ts deleted file mode 100644 index 29f6eab..0000000 --- a/src/middleware/endpointData.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { SystemPromptChoice } from "../types/grok"; -import { EndpointConfig } from "../types/server"; -import { getEndpointHandler, nullifyEndpointHandler } from "../utils/Endpoint"; -import { config } from "../config/config"; -import { getLogger } from "../utils/Logger"; - -declare global { - namespace Express { - interface Request { - endpointConfig?: EndpointConfig; - } - } -} - -export const endpointData = ( - req: Request, - res: Response, - next: NextFunction -) => { - try { - const endpointHandler = getEndpointHandler(); - const source = endpointHandler.getSource(); - const mainEndpoint = endpointHandler.getMain(); - const child = endpointHandler.getChild(); - - switch (source) { - case "leveret": - let endpointSpecificConfig; - if (mainEndpoint) { - const configEntry = config.ENDPOINTS[mainEndpoint]; - if (configEntry) { - endpointSpecificConfig = { - endpointName: mainEndpoint, - systemPrompt: (child as SystemPromptChoice) || "default", - model: configEntry.MODEL, - maxPromptTokens: configEntry.MAX_PROMPT_TK, - maxContextTokens: configEntry.MAX_CONTEXT_TK, - maxTotalTokens: configEntry.MAX_TOTAL_TK, - filters: configEntry.FILTERS, - rateLimit: configEntry.RATE_LIMIT, - }; - } else { - getLogger().simpleLog( - "warn", - `No config found for endpoint: ${mainEndpoint}` - ); - } - } - - req.endpointConfig = { - source: endpointHandler.getSource(), - main: mainEndpoint, - child: child, - config: endpointSpecificConfig, - }; - break; - - case "data": - req.endpointConfig = { - source: source, - main: mainEndpoint, - child: child, - }; - break; - } - - nullifyEndpointHandler(); - next(); - } catch (err) { - getLogger().simpleLog("error", `endpointData error: ${err}`); - next(err); - } -}; diff --git a/src/middleware/endpoints/aiEndpoint.ts b/src/middleware/endpoints/aiEndpoint.ts new file mode 100644 index 0000000..f2f391e --- /dev/null +++ b/src/middleware/endpoints/aiEndpoint.ts @@ -0,0 +1,42 @@ +import "../../types/express"; + +import type { NextFunction, Request, Response } from "express"; + +import { config } from "../../config/config"; +import type { AiModel, SystemPrompt } from "../../config/routes"; +import { AI_MODELS, SYSTEM_PROMPTS } from "../../config/routes"; + +export const aiEndpoint = (req: Request, res: Response, next: NextFunction) => { + const { model, systemPrompt } = req.params; + + const isValidModel = AI_MODELS.includes(model as AiModel); + if (!isValidModel) { + res.status(404).json({ + error: "Model not found", + message: `Model must be one of: ${AI_MODELS.join(", ")}`, + }); + return; + } + + const isValidPrompt = SYSTEM_PROMPTS.includes(systemPrompt as SystemPrompt); + if (!isValidPrompt) { + res.status(400).json({ + error: "Invalid system prompt", + message: `System prompt must be one of: ${SYSTEM_PROMPTS.join(", ")}`, + }); + return; + } + + const modelConfig = config.ENDPOINTS[model]; + if (!modelConfig) { + res.status(500).json({ error: "Model configuration missing" }); + return; + } + + req.ai = { + model: model as AiModel, + systemPrompt: systemPrompt as SystemPrompt, + }; + + next(); +}; diff --git a/src/middleware/endpoints/loadEndpoint.ts b/src/middleware/endpoints/loadEndpoint.ts new file mode 100644 index 0000000..28da2de --- /dev/null +++ b/src/middleware/endpoints/loadEndpoint.ts @@ -0,0 +1,8 @@ +import type { NextFunction, Request, Response } from "express"; + +export const loadEndpoint = (req: Request, res: Response, next: NextFunction) => { + const endpoint = `${req.baseUrl}${req.path}`; + req.endpoint = endpoint; + + next(); +}; diff --git a/src/middleware/endpoints/membersEndpoint.ts b/src/middleware/endpoints/membersEndpoint.ts new file mode 100644 index 0000000..720d51b --- /dev/null +++ b/src/middleware/endpoints/membersEndpoint.ts @@ -0,0 +1,7 @@ +import "../../types/express"; + +import type { NextFunction, Request, Response } from "express"; + +export const membersEndpoint = (req: Request, res: Response, next: NextFunction) => { + next(); +}; diff --git a/src/middleware/endpoints/oredicEndpoint.ts b/src/middleware/endpoints/oredicEndpoint.ts new file mode 100644 index 0000000..7bd3886 --- /dev/null +++ b/src/middleware/endpoints/oredicEndpoint.ts @@ -0,0 +1,35 @@ +import "../../types/express"; + +import type { NextFunction, Request, Response } from "express"; + +import type { OredicAction, OredicPack } from "../../config/routes"; +import { OREDIC_ACTIONS, OREDIC_PACKS } from "../../config/routes"; + +export const oredicEndpoint = (req: Request, res: Response, next: NextFunction) => { + const { pack, action } = req.params; + + const isValidPack = OREDIC_PACKS.includes(pack as OredicPack); + if (!isValidPack) { + res.status(404).json({ + error: "Invalid pack", + message: `Pack must be one of: ${OREDIC_PACKS.join(", ")}`, + }); + return; + } + + const isValidAction = OREDIC_ACTIONS.includes(action as OredicAction); + if (!isValidAction) { + res.status(404).json({ + error: "Invalid action", + message: `Action must be one of: ${OREDIC_ACTIONS.join(", ")}`, + }); + return; + } + + req.oredic = { + pack: pack as OredicPack, + action: action as OredicAction, + }; + + next(); +}; diff --git a/src/middleware/filterBody.ts b/src/middleware/filterBody.ts deleted file mode 100644 index 1201734..0000000 --- a/src/middleware/filterBody.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { filtersConfig } from "../config/config"; -import { FILTERS } from "../loaders/filters"; -import { getLogger } from "../utils/Logger"; -import { getDbHandler } from "../db/DbHandler"; - -//TODO: Make the config a map for Severity -> Action, and handle that. -export const filterBody = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - const prompt = req.body.prompt; - const context = req.body.context; - const endpointConfig = req.endpointConfig?.config; - const endpointName = endpointConfig?.endpointName; - if (!endpointName) { - res.status(500).json("no endpoint name, this shouldn't ever happen..."); - return; - } - const username = await getDbHandler().getUsername( - req.body.userId, - endpointName - ); - - switch (filtersConfig.grok.filterAction) { - case "warn": - filterAndWarn(prompt, username); - filterAndWarn(context, username); - next(); - break; - case "replace": - req.body.prompt = filterAndReplace(prompt, username); - req.body.context = filterAndReplace(context, username); - next(); - break; - case "block": - const promptBlocked = filterAndBlock(prompt, username); - const contextBlocked = filterAndBlock(context, username); - - if (!promptBlocked || !contextBlocked) { - res.status(403).json({ - error: "Content Violation", - message: filtersConfig.grok.customMessage, - }); - return; - } - next(); - break; - } -}; - -function filterAndWarn(text: string, username: string) { - FILTERS.forEach((filter) => { - if (filter.pattern.test(text)) { - getLogger().simpleLog( - "warn", - `${filter.description} (${filter.severity}) - User: ${username}` - ); - } - }); -} - -function filterAndReplace(text: string, username: string): string { - let result = text; - FILTERS.forEach((filter) => { - if (filter.pattern.test(result)) { - getLogger().simpleLog( - "warn", - `${filter.description} (${filter.severity}) - User: ${username}` - ); - result = result.replace(filter.pattern, "[removed]"); - } - }); - return result; -} - -function filterAndBlock(text: string, username: string): boolean { - for (const filter of FILTERS) { - if (filter.pattern.test(text)) { - getLogger().simpleLog( - "warn", - `BLOCKED: ${filter.description} (${filter.severity}) - User: ${username}` - ); - return false; - } - } - return true; -} diff --git a/src/middleware/globalRateLimits.ts b/src/middleware/globalRateLimits.ts deleted file mode 100644 index 983f093..0000000 --- a/src/middleware/globalRateLimits.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { getDbHandler } from "../db/DbHandler"; -import { getLogger } from "../utils/Logger"; - -export const globalRateLimits = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - const endpointConfig = req.endpointConfig?.config; - - if (!endpointConfig) { - res.status(500).json({ error: "Endpoint configuration not found" }); - return; - } - - const isAllowed = await checkRateLimits( - req.body.userId, - endpointConfig.endpointName, - endpointConfig.rateLimit.USER.WHITELIST - ); - if (!isAllowed) { - getLogger().simpleLog( - "warn", - `Global Rate Limit Reached for ${endpointConfig.endpointName}` - ); - res - .status(429) - .json({ error: "You're Being Rate Limited by the Global Rate Limit" }); - return; - } - await getDbHandler().updateGlobalRates(endpointConfig.endpointName, "take"); - next(); -}; - -async function checkRateLimits( - dcUserId: string, - endpointName: string, - whitelist: string[] -) { - if (whitelist.includes(dcUserId)) return true; - const rates = await getDbHandler().getGlobalRates(endpointName); - if (rates === null) return false; - return rates > 0; -} diff --git a/src/middleware/jsonWithRawBody.ts b/src/middleware/jsonWithRawBody.ts deleted file mode 100644 index f402a89..0000000 --- a/src/middleware/jsonWithRawBody.ts +++ /dev/null @@ -1,9 +0,0 @@ -import express from "express"; - -export function jsonWithRawBody() { - return express.json({ - verify: (req: any, _res, buf) => { - req.rawBody = Buffer.from(buf); - }, - }); -} diff --git a/src/middleware/leveretAuth.ts b/src/middleware/leveretAuth.ts deleted file mode 100644 index ee89cd7..0000000 --- a/src/middleware/leveretAuth.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { config } from "../config/config"; -import { createHash, verify } from "crypto"; -import { LEVERET_PUBLIC_KEY } from "../loaders/keys"; -import { getLogger } from "../utils/Logger"; - -export const leveretAuth = ( - req: Request, - res: Response, - next: NextFunction -): void => { - const authResult = authRequest(req); - - if (!authResult) { - getLogger().simpleLog("warn", "Unauthorized Request For Leveret"); - res.status(401).json({ error: "Unauthorized" }); - return; - } - next(); -}; - -const HEADERS = { - signature: "x-leveret-signature-ed25519", - tag: "x-leveret-tag", - timestamp: "x-leveret-timestamp", - requestId: "x-leveret-request-id", -}; - -function authRequest(req: any) { - if (config.SKIP_LEVERET_AUTH) return true; - - // Mostly from https://gist.github.com/NotMyWing/632d738644c17aa71931169af5cb2767 - const headers = req.headers ?? {}; - const signatureB64 = headers[HEADERS.signature]; - const tagName = headers[HEADERS.tag]; - const timestamp = headers[HEADERS.timestamp]; - const requestId = headers[HEADERS.requestId]; - - // Do less expensive checks first to save on potential compute - if (!signatureB64 || !tagName) { - getLogger().simpleLog( - "warn", - "LEVE_AUTH: Could not find signatureB64 nor tagName" - ); - return false; - } - if (!config.ACCEPTED_TAGS.includes(tagName)) { - getLogger().simpleLog( - "warn", - "LEVE_AUTH: Could not find tagName in the accepted tags" - ); - return false; - } - - const method = String(req.method ?? "GET").toUpperCase(); - const host = req.get?.("host") ?? headers.host; - if (!host) { - getLogger().simpleLog("warn", "LEVE_AUTH: Could not find host"); - return false; - } - - const url = `${req.protocol ?? "https"}://${host}${ - req.originalUrl ?? req.url ?? "" - }`; - const bodyHash = createHash("sha256").update(req.rawBody).digest("hex"); - const canonical = [timestamp, requestId, method, url, bodyHash].join("\n"); - - const signature = Buffer.from(signatureB64, "base64"); - const valid = verify( - null, - Buffer.from(canonical), - LEVERET_PUBLIC_KEY, - signature - ); - if (!valid) { - getLogger().simpleLog("warn", "LEVE_AUTH: Invalid signature"); - return false; - } - - return true; -} diff --git a/src/middleware/sanity/aiTokenChecker.ts b/src/middleware/sanity/aiTokenChecker.ts new file mode 100644 index 0000000..df8297e --- /dev/null +++ b/src/middleware/sanity/aiTokenChecker.ts @@ -0,0 +1,52 @@ +import type { NextFunction, Request, Response } from "express"; + +import { config } from "../../config/config"; +import { getLogger } from "../../helpers/Logger"; +import { findDcUsernameById } from "../../utils/bot/usernames"; +import { tokenizeGrok } from "../../utils/grok/grok"; + +export const aiTokenChecker = async (req: Request, res: Response, next: NextFunction): Promise => { + const aiModel = req.ai?.model; + + if (!aiModel) { + res.status(500).json({ error: "AI model not found" }); + return; + } + + const modelConfig = config.ENDPOINTS[aiModel]; + if (!modelConfig) { + res.status(500).json({ error: "Model configuration not found" }); + return; + } + + // TODO: Implement model-specific tokenization based on aiModel + // For now, using Grok tokenizer as default + const tokenizedPrompt = await tokenizeGrok(req.body.prompt, modelConfig.MODEL); + const tokenizedContext = await tokenizeGrok(req.body.context, modelConfig.MODEL); + + if (!tokenizedPrompt || !tokenizedContext) { + res.status(502).json({ error: "AI Tokenizer errored" }); + return; + } + + if (tokenizedPrompt.tokenCount > modelConfig.MAX_PROMPT_TK) { + res.status(403).json({ error: "Token count exceeds the maximum allowed limit" }); + return; + } + + if (tokenizedContext.tokenCount > modelConfig.MAX_CONTEXT_TK) { + res.status(403).json({ error: "Context count exceeds the maximum allowed limit" }); + return; + } + + const totalTokens = tokenizedPrompt.tokenCount + tokenizedContext.tokenCount; + const username = await findDcUsernameById(req.body.userId); + getLogger().simpleLog( + "telemetry", + `${username} passed a request for ${totalTokens} tokens on ${aiModel}`, + ); + next(); +}; + +// Legacy export for backwards compatibility +export const tokenCheckerGrok = aiTokenChecker; diff --git a/src/middleware/sanity/cleanup.ts b/src/middleware/sanity/cleanup.ts new file mode 100644 index 0000000..bfac2f8 --- /dev/null +++ b/src/middleware/sanity/cleanup.ts @@ -0,0 +1,22 @@ +import type { NextFunction, Request, Response } from "express"; + +import { getDbHandler } from "../../db/DbHandler"; + +export const cleanup = async (req: Request, res: Response, next: NextFunction): Promise => { + const isLeveret = req.flags?.isLeveret; + + if (isLeveret) { + const userId = req.body.userId; + const aiModel = req.ai?.model; + + if (aiModel) { + await getDbHandler().updateGlobalRates(aiModel, "take"); + await getDbHandler().updateUserRates(userId, aiModel, "take"); + } + } + + res.json({ + ...res.data, + timing: res.timing, + }); +}; diff --git a/src/middleware/sanity/filterBody.ts b/src/middleware/sanity/filterBody.ts new file mode 100644 index 0000000..b655c37 --- /dev/null +++ b/src/middleware/sanity/filterBody.ts @@ -0,0 +1,78 @@ +import type { NextFunction, Request, Response } from "express"; + +import { filtersConfig } from "../../config/config"; +import { getDbHandler } from "../../db/DbHandler"; +import { getLogger } from "../../helpers/Logger"; +import { FILTERS } from "../../loaders/filters"; + +//TODO: Make the config a map for Severity -> Action, and handle that. +export const filterBody = async (req: Request, res: Response, next: NextFunction): Promise => { + const prompt = req.body.prompt; + const context = req.body.context; + const aiModel = req.ai?.model; + + if (!aiModel) { + res.status(500).json("AI model not found"); + return; + } + + const username = await getDbHandler().getUsername(req.body.userId, aiModel); + + switch (filtersConfig.grok.filterAction) { + case "warn": + filterAndWarn(prompt, username); + filterAndWarn(context, username); + next(); + break; + case "replace": + req.body.prompt = filterAndReplace(prompt, username); + req.body.context = filterAndReplace(context, username); + next(); + break; + case "block": + const promptBlocked = filterAndBlock(prompt, username); + const contextBlocked = filterAndBlock(context, username); + + if (!promptBlocked || !contextBlocked) { + res.status(403).json({ + error: "Content Violation", + message: filtersConfig.grok.customMessage, + }); + return; + } + next(); + break; + } +}; + +function filterAndWarn(text: string, username: string) { + FILTERS.forEach((filter) => { + if (filter.pattern.test(text)) { + getLogger().simpleLog("warn", `${filter.description} (${filter.severity}) - User: ${username}`); + } + }); +} + +function filterAndReplace(text: string, username: string): string { + let result = text; + FILTERS.forEach((filter) => { + if (filter.pattern.test(result)) { + getLogger().simpleLog("warn", `${filter.description} (${filter.severity}) - User: ${username}`); + result = result.replace(filter.pattern, "[removed]"); + } + }); + return result; +} + +function filterAndBlock(text: string, username: string): boolean { + for (const filter of FILTERS) { + if (filter.pattern.test(text)) { + getLogger().simpleLog( + "warn", + `BLOCKED: ${filter.description} (${filter.severity}) - User: ${username}`, + ); + return false; + } + } + return true; +} diff --git a/src/middleware/sanity/jsonWithRawBody.ts b/src/middleware/sanity/jsonWithRawBody.ts new file mode 100644 index 0000000..afc2213 --- /dev/null +++ b/src/middleware/sanity/jsonWithRawBody.ts @@ -0,0 +1,9 @@ +import express from "express"; + +export function jsonWithRawBody() { + return express.json({ + verify: (req: any, _res, buf) => { + req.rawBody = Buffer.from(buf); + }, + }); +} diff --git a/src/middleware/sanity/validator.ts b/src/middleware/sanity/validator.ts new file mode 100644 index 0000000..6d2b349 --- /dev/null +++ b/src/middleware/sanity/validator.ts @@ -0,0 +1,26 @@ +import type { NextFunction, Request, Response } from "express"; +import type { ZodObject } from "zod"; + +import { getLogger } from "../../helpers/Logger"; + +export const zodValidator = (schema: ZodObject) => { + return (req: Request, res: Response, next: NextFunction) => { + const parsed = schema.safeParse(req.body); + if (!parsed.success) { + const errorDetails = parsed.error.issues.map((e: any) => ({ + field: e.path.join("."), + message: e.message, + })); + + getLogger().simpleLog("warn", `Zod validation failed: ${JSON.stringify(errorDetails)}`); + + res.status(400).json({ + error: "Validation Error", + details: errorDetails, + }); + return; + } + req.body = parsed.data; + next(); + }; +}; diff --git a/src/middleware/setEndpointData.ts b/src/middleware/setEndpointData.ts deleted file mode 100644 index a0af6a5..0000000 --- a/src/middleware/setEndpointData.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { - getEndpointHandler, - initEndpointHandler, - nullifyEndpointHandler, -} from "../utils/Endpoint"; -import { EndpointLevel } from "../types/server"; -import { getLogger } from "../utils/Logger"; - -export const setEndpointData = (level: EndpointLevel, data: string) => { - return (req: Request, res: Response, next: NextFunction) => { - try { - switch (level) { - case "source": - initEndpointHandler(data); - break; - - case "main": - getEndpointHandler().setMain(data); - break; - - case "child": - getEndpointHandler().setChild(data); - break; - } - next(); - } catch (err) { - getLogger().simpleLog("error", `setEndpointData error: ${err}`); - res - .status(500) - .json({ - error: "Internal Server Error - Endpoint configuration failed", - }); - } - }; -}; diff --git a/src/middleware/tokenChecker.ts b/src/middleware/tokenChecker.ts deleted file mode 100644 index df3fa77..0000000 --- a/src/middleware/tokenChecker.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { getLogger } from "../utils/Logger"; -import { tokenizeGrok } from "../utils/grok/grok"; -import { findDcUsernameById } from "../utils/bot/usernames"; - -export const tokenCheckerGrok = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - const endpointConfig = req.endpointConfig?.config; - - if (!endpointConfig) { - res.status(500).json({ error: "Endpoint configuration not found" }); - return; - } - - const tokenizedPrompt = await tokenizeGrok( - req.body.prompt, - endpointConfig.model - ); - const tokenizedContext = await tokenizeGrok( - req.body.context, - endpointConfig.model - ); - - if (!tokenizedPrompt || !tokenizedContext) { - res.status(502).json("Grok Tokenizer Errored"); - return; - } - - if (tokenizedPrompt.tokenCount > endpointConfig.maxPromptTokens) { - res.status(403).json("Token count exceeds the maximum allowed limit"); - return; - } - - if (tokenizedContext.tokenCount > endpointConfig.maxContextTokens) { - res.status(403).json("Context count exceeds the maximum allowed limit"); - return; - } - - getLogger().simpleLog( - "info", - `${await findDcUsernameById(req.body.userId)} passed a request for ${ - tokenizedPrompt.tokenCount + tokenizedContext.tokenCount - } tokens on ${endpointConfig.endpointName}` - ); - next(); -}; diff --git a/src/middleware/userRateLimits.ts b/src/middleware/userRateLimits.ts deleted file mode 100644 index 73e98db..0000000 --- a/src/middleware/userRateLimits.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { getDbHandler } from "../db/DbHandler"; -import { getLogger } from "../utils/Logger"; - -export const userRateLimits = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - const userId = req.body.userId; - const endpointConfig = req.endpointConfig?.config; - - if (!endpointConfig) { - res.status(500).json({ error: "Endpoint configuration not found" }); - return; - } - - const isAllowed = await checkRateLimits( - userId, - endpointConfig.endpointName, - endpointConfig.rateLimit.USER.WHITELIST - ); - - if (!isAllowed) { - const username = await getDbHandler().getUsername( - userId, - endpointConfig.endpointName - ); - getLogger().simpleLog( - "warn", - `User: ${username} got rate limited on ${endpointConfig.endpointName}` - ); - res.status(429).json({ error: "You're Being Rate Limited" }); - return; - } - - next(); -}; - -async function checkRateLimits( - dcUserId: string, - endpointName: string, - whitelist: string[] -) { - if (whitelist.includes(dcUserId)) return true; - const rates = await getDbHandler().getUserRates(dcUserId, endpointName); - return rates > 0; -} diff --git a/src/middleware/validator.ts b/src/middleware/validator.ts deleted file mode 100644 index b0c0856..0000000 --- a/src/middleware/validator.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { ZodError, ZodObject } from "zod"; -import { getLogger } from "../utils/Logger"; - -export const zodValidator = (schema: ZodObject) => { - return (req: Request, res: Response, next: NextFunction) => { - const parsed = schema.safeParse(req.body); - if (!parsed.success) { - const errorDetails = parsed.error.issues.map((e: any) => ({ - field: e.path.join("."), - message: e.message, - })); - - getLogger().simpleLog( - "warn", - `Zod validation failed: ${JSON.stringify(errorDetails)}` - ); - - res.status(400).json({ - error: "Validation Error", - details: errorDetails, - }); - return; - } - req.body = parsed.data; - next(); - }; -}; diff --git a/src/routes/ai.ts b/src/routes/ai.ts new file mode 100644 index 0000000..56d7c03 --- /dev/null +++ b/src/routes/ai.ts @@ -0,0 +1,38 @@ +import { Router } from "express"; + +import { AiController } from "../controllers/AiController"; +import { timerStop } from "../middleware/analytics/perf/timer"; +import { globalRateLimits } from "../middleware/auth/globalRateLimits"; +import { leveretAuth } from "../middleware/auth/leveretAuth"; +import { userRateLimits } from "../middleware/auth/userRateLimits"; +import { aiEndpoint } from "../middleware/endpoints/aiEndpoint"; +import { aiTokenChecker } from "../middleware/sanity/aiTokenChecker"; +import { cleanup } from "../middleware/sanity/cleanup"; +import { filterBody } from "../middleware/sanity/filterBody"; +import { jsonWithRawBody } from "../middleware/sanity/jsonWithRawBody"; +import { zodValidator } from "../middleware/sanity/validator"; +import { AiInputDataSchema } from "../types/ai"; + +export function aiRoutes() { + const router = Router(); + const controller = new AiController(); + + router.use(jsonWithRawBody()); + router.use(leveretAuth); + + router.post( + "/:model/:systemPrompt", + aiEndpoint, + zodValidator(AiInputDataSchema), + globalRateLimits, + userRateLimits, + filterBody, + aiTokenChecker, + controller.prompt, + ); + + router.use(timerStop); + router.use(cleanup); + + return router; +} diff --git a/src/routes/data/data.ts b/src/routes/data/data.ts deleted file mode 100644 index 8bbc127..0000000 --- a/src/routes/data/data.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Router } from "express"; -import { setEndpointData } from "../../middleware/setEndpointData"; -import { membersRoutes } from "./members/members"; - -export function dataRoutes() { - const router = Router(); - - router.use("/members", setEndpointData("main", "members"), membersRoutes()); - - return router; -} diff --git a/src/routes/data/members/members.ts b/src/routes/data/members/members.ts deleted file mode 100644 index cb8b8e8..0000000 --- a/src/routes/data/members/members.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Router } from "express"; -import { cleanup } from "../../../middleware/cleanup"; -import { endpointData } from "../../../middleware/endpointData"; -import { setEndpointData } from "../../../middleware/setEndpointData"; -import { MembersController } from "../../../controllers/MembersController"; - -export function membersRoutes() { - const router = Router(); - const controller = new MembersController(); - - router.get( - "/ids", - setEndpointData("child", "ids"), - endpointData, - cleanup, - controller.handler() - ); - router.get( - "/usernames", - setEndpointData("child", "usernames"), - endpointData, - cleanup, - controller.handler() - ); - router.get( - "/usernames-and-ids", - setEndpointData("child", "usernames-and-ids"), - endpointData, - cleanup, - controller.handler() - ); - - return router; -} diff --git a/src/routes/index.ts b/src/routes/index.ts index f802abd..6dce86c 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,22 +1,20 @@ import { Router } from "express"; -import { grokRoutes } from "./leveret/grok/grok"; -import { leveretRoutes } from "./leveret/leveret"; -import { leveretAuth } from "../middleware/leveretAuth"; -import { jsonWithRawBody } from "../middleware/jsonWithRawBody"; -import { initEndpointHandler } from "../utils/Endpoint"; -import { setEndpointData } from "../middleware/setEndpointData"; -import { dataRoutes } from "./data/data"; + +import { timerStart } from "../middleware/analytics/perf/timer"; +import { loadEndpoint } from "../middleware/endpoints/loadEndpoint"; +import { aiRoutes } from "./ai"; +import { membersRoutes } from "./members"; +import { oredicRoutes } from "./oredic"; export function routes() { - const router = Router(); - router.use( - "/leveret", - jsonWithRawBody(), - setEndpointData("source", "leveret"), - leveretAuth, - leveretRoutes() - ); + const router = Router(); + + router.use(timerStart); + router.use(loadEndpoint); + + router.use("/ai", aiRoutes()); + router.use("/members", membersRoutes()); + router.use("/oredic", oredicRoutes()); - router.use("/data", setEndpointData("source", "data"), dataRoutes()); - return router; + return router; } diff --git a/src/routes/leveret/grok/grok.ts b/src/routes/leveret/grok/grok.ts deleted file mode 100644 index 7935922..0000000 --- a/src/routes/leveret/grok/grok.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Router } from "express"; -import { GrokController } from "../../../controllers/GrokController"; -import { userRateLimits } from "../../../middleware/userRateLimits"; -import { globalRateLimits } from "../../../middleware/globalRateLimits"; -import { zodValidator } from "../../../middleware/validator"; -import { GrokInputDataSchema } from "../../../types/grok"; -import { cleanup } from "../../../middleware/cleanup"; -import { tokenCheckerGrok } from "../../../middleware/tokenChecker"; -import { setEndpointData } from "../../../middleware/setEndpointData"; -import { endpointData } from "../../../middleware/endpointData"; -import { filterBody } from "../../../middleware/filterBody"; - -export function grokRoutes() { - const router = Router(); - const controller = new GrokController(); - - router.post( - "/simple", - setEndpointData("child", "simple"), - endpointData, - zodValidator(GrokInputDataSchema), - globalRateLimits, - userRateLimits, - filterBody, - tokenCheckerGrok, - cleanup, - controller.handler() - ); - router.post( - "/hogichan", - setEndpointData("child", "hogichan"), - endpointData, - zodValidator(GrokInputDataSchema), - globalRateLimits, - userRateLimits, - filterBody, - tokenCheckerGrok, - cleanup, - controller.handler() - ); - router.post( - "/nomicord", - setEndpointData("child", "nomicord"), - endpointData, - zodValidator(GrokInputDataSchema), - globalRateLimits, - filterBody, - tokenCheckerGrok, - cleanup, - controller.handler() - ); - - return router; -} diff --git a/src/routes/leveret/leveret.ts b/src/routes/leveret/leveret.ts deleted file mode 100644 index 7ebf16b..0000000 --- a/src/routes/leveret/leveret.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Router } from "express"; -import { grokRoutes } from "./grok/grok"; -import { setEndpointData } from "../../middleware/setEndpointData"; - -export function leveretRoutes() { - const router = Router(); - - router.use("/grok", setEndpointData("main", "grok"), grokRoutes()); - - router.use( - "/premium-grok", - setEndpointData("main", "premium-grok"), - grokRoutes() - ); - - return router; -} diff --git a/src/routes/members.ts b/src/routes/members.ts new file mode 100644 index 0000000..cafa1a9 --- /dev/null +++ b/src/routes/members.ts @@ -0,0 +1,20 @@ +import { Router } from "express"; + +import { MembersController } from "../controllers/MembersController"; +import { timerStop } from "../middleware/analytics/perf/timer"; +import { membersEndpoint } from "../middleware/endpoints/membersEndpoint"; +import { cleanup } from "../middleware/sanity/cleanup"; + +export function membersRoutes() { + const router = Router(); + const controller = new MembersController(); + + router.get("/user-ids", membersEndpoint, controller.getIds); + router.get("/usernames", membersEndpoint, controller.getUsernames); + router.get("/users", membersEndpoint, controller.getUsers); + + router.use(timerStop); + router.use(cleanup); + + return router; +} diff --git a/src/routes/oredic.ts b/src/routes/oredic.ts new file mode 100644 index 0000000..f31ad38 --- /dev/null +++ b/src/routes/oredic.ts @@ -0,0 +1,21 @@ +import { Router } from "express"; + +import { OredicController } from "../controllers/OredicController"; +import { timerStop } from "../middleware/analytics/perf/timer"; +import { oredicEndpoint } from "../middleware/endpoints/oredicEndpoint"; +import { cleanup } from "../middleware/sanity/cleanup"; +import { jsonWithRawBody } from "../middleware/sanity/jsonWithRawBody"; + +export function oredicRoutes() { + const router = Router(); + const controller = new OredicController(); + + router.use(jsonWithRawBody()); + + router.post("/:pack/:action", oredicEndpoint, controller.simplify); + + router.use(timerStop); + router.use(cleanup); + + return router; +} diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index e291574..0000000 --- a/src/server.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { initExpress } from "./express"; -import { getDiscordClient, initDiscordClient } from "./clients/DiscordClient"; -import { initGrokClient } from "./clients/GrokClient"; -import { getRedisClient, initRedisClient } from "./clients/RedisClient"; -import { config, env } from "./config/config"; -import { getDbHandler, initDbHandler } from "./db/DbHandler"; -import { LEVERET_PUBLIC_KEY } from "./loaders/keys"; -import { - startGlobalRateLimitIncrement, - startUserRateLimitIncrements, -} from "./services/rateLimits"; -import { getLogger, initLogger } from "./utils/Logger"; -import { startTimer, stopTimer } from "./utils/Timer"; -import { initDiscord } from "./discord"; - -async function main() { - startTimer("main"); - - initLogger(); - - getLogger().formattingLog("Clients Init"); - initRedisClient(); - await getRedisClient().connect(); - initGrokClient(); - - getLogger().formattingLog("DB Init"); - initDbHandler(); - await getDbHandler().init(); - - getLogger().formattingLog("Discord Bot Init"); - await initDiscord(); - - getLogger().formattingLog("Services Init"); - startGlobalRateLimitIncrement(); - await startUserRateLimitIncrements(); - - getLogger().formattingLog("App Init"); - const expressApp = initExpress(); - - expressApp.listen(config.PORT, config.RUNNING_IP, () => { - getLogger().formattingLog("Server Ready"); - getLogger().simpleLog( - "info", - `Server is running at http://${config.RUNNING_IP}:${config.PORT}` - ); - const main_timeTaken_ms = stopTimer("main").getTime(); - getLogger().simpleLog( - "info", - `Server took ${main_timeTaken_ms}ms to start` - ); - }); -} - -main(); diff --git a/src/services/ai/GrokService.ts b/src/services/ai/GrokService.ts new file mode 100644 index 0000000..421a300 --- /dev/null +++ b/src/services/ai/GrokService.ts @@ -0,0 +1,29 @@ +import { getGrokClient } from "../../clients/GrokClient"; +import { config } from "../../config/config"; +import type { AiModel, SystemPrompt } from "../../config/routes"; +import { DEFAULT_PROMPT, HOGICHAN_PROMPT, NOMICORD_PROMPT } from "../../loaders/storage"; +import type { AiInputData } from "../../types/ai"; +import { formatCompletion } from "../../utils/grok/formatter"; + +const SYSTEM_PROMPTS = { + default: DEFAULT_PROMPT, + hogichan: HOGICHAN_PROMPT, + nomicord: NOMICORD_PROMPT, +} as const; + +export class GrokService { + async generateCompletion(input: AiInputData, model: AiModel, systemPrompt: SystemPrompt) { + const modelConfig = config.ENDPOINTS[model]; + if (!modelConfig) { + throw new Error(`Model configuration not found for ${model}`); + } + + const completionParams = await formatCompletion(input, modelConfig.MODEL, systemPrompt, model); + + return getGrokClient().client.chat.completions.create(completionParams); + } + + getSystemPrompt(type: SystemPrompt): string { + return SYSTEM_PROMPTS[type]; + } +} diff --git a/src/services/members/MembersService.ts b/src/services/members/MembersService.ts new file mode 100644 index 0000000..094cd1d --- /dev/null +++ b/src/services/members/MembersService.ts @@ -0,0 +1,30 @@ +import { getDbHandler } from "../../db/DbHandler"; +import { findDcUsernameById } from "../../utils/bot/usernames"; + +export class MembersService { + private db = getDbHandler(); + + async getIds(): Promise { + return this.db.getHogMembers(); + } + + async getUsernames(): Promise { + const ids = await this.db.getHogMembers(); + const usernames = await Promise.all(ids.map((id) => findDcUsernameById(id))); + return usernames.filter((name): name is string => name !== null); + } + + async getUsers(): Promise> { + const ids = await this.db.getHogMembers(); + const result: Record = {}; + + await Promise.all( + ids.map(async (id) => { + const username = await findDcUsernameById(id); + result[id] = username || "unknown"; + }), + ); + + return result; + } +} diff --git a/src/services/oredic/OredicService.ts b/src/services/oredic/OredicService.ts new file mode 100644 index 0000000..25e2de8 --- /dev/null +++ b/src/services/oredic/OredicService.ts @@ -0,0 +1,82 @@ +import type { OredicPack } from "../../config/routes"; +import type { StandardError } from "../../types/errors"; +import type { AstNode, BuildOptions, OredicMatches } from "../../types/parsing"; +import { ErrorProne } from "../../utils/parentClasses/ErrorProne"; +import { OredicBuilder } from "../../utils/parsers/OredicBuilder"; +import { OredicMatcher } from "../../utils/parsers/OredicMatcher"; +import { OredicParser } from "../../utils/parsers/OredicParser"; + +export class OredicService extends ErrorProne { + constructor(private pack: OredicPack) { + super("OredicService"); + } + + private parser = new OredicParser(this.pack); + private matcher = new OredicMatcher(this.pack); + private builder = new OredicBuilder(this.pack); + + async parse(input: string): Promise { + const parseResult = this.parser.parse(input); + if (!parseResult) { + return this.setError(500, "Unknown Error: Could not parse input", "parse"); + } + return parseResult; + } + + async match(ast: AstNode): Promise { + const matchResult = this.matcher.match(ast); + return matchResult; + } + + async buildAsts(matches: OredicMatches, options: BuildOptions): Promise { + const builtAst = this.builder.buildFromMatches(matches, options); + return builtAst; + } + + async buildSingleAst(matches: OredicMatches, options: BuildOptions): Promise { + let optionsCount = 0; + + for (const option in options) { + if (options[option as keyof BuildOptions] === true) { + optionsCount += 1; + } + } + + if (optionsCount !== 1) { + return this.setError(400, "Must select one and only one way to build the ast.", "buildSingleAst"); + } + + const builtAst = this.builder.buildFromMatches(matches, options); + + if (this.isError(builtAst)) { + return this.propagateError(builtAst, "Failed to build AST", "buildSingleAst"); + } + + if (builtAst.length === 0) { + return this.setError(400, "No AST was built", "buildSingleAst"); + } + + return builtAst[0]; + } + + async build(ast: AstNode): Promise { + return this.builder.buildFromAst(ast); + } + + async buildAndFindBest(asts: AstNode[]): Promise { + let candidates: string[] = []; + + for (const ast of asts) { + const candidate = this.builder.buildFromAst(ast); + + if (this.isError(candidate)) { + return this.propagateError(candidate, "Failed to find best string from Ast"); + } + + candidates.push(candidate); + } + + candidates = candidates.sort((a, b) => a.length - b.length); + return candidates[0]; + } +} diff --git a/src/services/rateLimits.ts b/src/services/rateLimits.ts index 7426836..67fb0a0 100644 --- a/src/services/rateLimits.ts +++ b/src/services/rateLimits.ts @@ -1,103 +1,86 @@ import { config } from "../config/config"; import { getDbHandler } from "../db/DbHandler"; -import { getRedisClient } from "../clients/RedisClient"; -import { getLogger } from "../utils/Logger"; +import { getLogger } from "../helpers/Logger"; const rateLimitIntervals: Map = new Map(); export function startGlobalRateLimitIncrement() { - for (const [endpointName, endpointConfig] of Object.entries( - config.ENDPOINTS - )) { - if (rateLimitIntervals.has(`${endpointName}:global`)) continue; + for (const [endpointName, endpointConfig] of Object.entries(config.ENDPOINTS)) { + if (rateLimitIntervals.has(`${endpointName}:global`)) continue; - const intervalMs = endpointConfig.RATE_LIMIT.GLOBAL.INTERVAL * 1000; + const intervalMs = endpointConfig.RATE_LIMIT.GLOBAL.INTERVAL * 1000; - const interval = setInterval(async () => { - try { - await getDbHandler().updateGlobalRates(endpointName, "give"); - } catch (err) { - getLogger().simpleLog( - "error", - `Failed to increment global rates for ${endpointName}: ${err}` - ); - } - }, intervalMs); + const interval = setInterval(async () => { + try { + await getDbHandler().updateGlobalRates(endpointName, "give"); + } catch (err) { + getLogger().simpleLog( + "error", + `Failed to increment global rates for ${endpointName}: ${err}`, + ); + } + }, intervalMs); - rateLimitIntervals.set(`${endpointName}:global`, interval); + rateLimitIntervals.set(`${endpointName}:global`, interval); - getLogger().simpleLog( - "success", - `Global rate limit increment started for ${endpointName} (every ${endpointConfig.RATE_LIMIT.GLOBAL.INTERVAL}s)` - ); - } + getLogger().simpleLog( + "success", + `Global rate limit increment started for ${endpointName} (every ${endpointConfig.RATE_LIMIT.GLOBAL.INTERVAL}s)`, + ); + } } export async function startUserRateLimitIncrements() { - for (const [endpointName, endpointConfig] of Object.entries( - config.ENDPOINTS - )) { - if (rateLimitIntervals.has(`${endpointName}:user`)) continue; + for (const [endpointName, endpointConfig] of Object.entries(config.ENDPOINTS)) { + if (rateLimitIntervals.has(`${endpointName}:user`)) continue; - const intervalMs = endpointConfig.RATE_LIMIT.USER.INTERVAL * 1000; + const intervalMs = endpointConfig.RATE_LIMIT.USER.INTERVAL * 1000; - const interval = setInterval(async () => { - try { - const keys = await getDbHandler().getAllUserKeysForEndpoint( - endpointName - ); + const interval = setInterval(async () => { + try { + const keys = await getDbHandler().getAllUserKeysForEndpoint(endpointName); - for (const key of keys) { - // Extract userId from key: "endpointName:user:userId" - const userId = key.split(":")[2]; - if (!userId) continue; + for (const key of keys) { + // Extract userId from key: "endpointName:user:userId" + const userId = key.split(":")[2]; + if (!userId) continue; - if (endpointConfig.RATE_LIMIT.USER.WHITELIST.includes(userId)) - continue; + if (endpointConfig.RATE_LIMIT.USER.WHITELIST.includes(userId)) continue; - await getDbHandler().updateUserRates(userId, endpointName, "give"); - } - } catch (err) { - getLogger().simpleLog( - "error", - `Failed to increment user rates for ${endpointName}: ${err}` - ); - } - }, intervalMs); + await getDbHandler().updateUserRates(userId, endpointName, "give"); + } + } catch (err) { + getLogger().simpleLog("error", `Failed to increment user rates for ${endpointName}: ${err}`); + } + }, intervalMs); - rateLimitIntervals.set(`${endpointName}:user`, interval); + rateLimitIntervals.set(`${endpointName}:user`, interval); - getLogger().simpleLog( - "success", - `User rate limit increment started for ${endpointName} (every ${endpointConfig.RATE_LIMIT.USER.INTERVAL}s)` - ); - } + getLogger().simpleLog( + "success", + `User rate limit increment started for ${endpointName} (every ${endpointConfig.RATE_LIMIT.USER.INTERVAL}s)`, + ); + } } export function stopGlobalRateLimitIncrement() { - for (const [key, interval] of rateLimitIntervals.entries()) { - if (key.endsWith(":global")) { - clearInterval(interval); - rateLimitIntervals.delete(key); - const endpointName = key.replace(":global", ""); - getLogger().simpleLog( - "info", - `Global rate limit increment stopped for ${endpointName}` - ); + for (const [key, interval] of rateLimitIntervals.entries()) { + if (key.endsWith(":global")) { + clearInterval(interval); + rateLimitIntervals.delete(key); + const endpointName = key.replace(":global", ""); + getLogger().simpleLog("info", `Global rate limit increment stopped for ${endpointName}`); + } } - } } export function stopUserRateLimitIncrements() { - for (const [key, interval] of rateLimitIntervals.entries()) { - if (key.endsWith(":user")) { - clearInterval(interval); - rateLimitIntervals.delete(key); - const endpointName = key.replace(":user", ""); - getLogger().simpleLog( - "info", - `User rate limit increments stopped for ${endpointName}` - ); + for (const [key, interval] of rateLimitIntervals.entries()) { + if (key.endsWith(":user")) { + clearInterval(interval); + rateLimitIntervals.delete(key); + const endpointName = key.replace(":user", ""); + getLogger().simpleLog("info", `User rate limit increments stopped for ${endpointName}`); + } } - } } diff --git a/src/types/ai.ts b/src/types/ai.ts new file mode 100644 index 0000000..38e0d27 --- /dev/null +++ b/src/types/ai.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; + +export const AiInputDataSchema = z.object({ + userId: z.string().min(1, "userId cannot be empty"), + prompt: z.string().min(1, "prompt cannot be empty").max(10000, "prompt too long"), + context: z.string().max(5000, "context too long"), + attachment: z.url("attachment must be a valid URL").nullable(), +}); +export type AiInputData = z.infer; + +export type ModelChoice = "grok-3-mini" | "grok-4-0709"; + +const GrokTokenizeReqSchema = z.object({ + token_ids: z.array( + z.object({ + token_id: z.number(), + string_token: z.string(), + token_bytes: z.array(z.number()), + }), + ), +}); +export type GrokTokenizeReq = z.infer; diff --git a/src/types/config.ts b/src/types/config.ts new file mode 100644 index 0000000..c3510ce --- /dev/null +++ b/src/types/config.ts @@ -0,0 +1,3 @@ +import type { ConfigSchema, FiltersSchema } from "../config/fileSchema"; + +export type ValidConfigSchema = ConfigSchema | FiltersSchema; diff --git a/src/types/errors.ts b/src/types/errors.ts new file mode 100644 index 0000000..16ae13d --- /dev/null +++ b/src/types/errors.ts @@ -0,0 +1,13 @@ +import type { PathLike } from "fs"; + +export type StandardError = { + type: "error"; + code: number | null; + status: boolean; + send: boolean; + message: string | null; + location: PathLike | null; + time: Date | null; + context?: string | null; // Method/class context where error occurred + stackTrace?: StandardError[]; // Chain of errors from deepest to shallowest +}; diff --git a/src/types/express.ts b/src/types/express.ts new file mode 100644 index 0000000..741cd94 --- /dev/null +++ b/src/types/express.ts @@ -0,0 +1,73 @@ +import type { Request } from "express"; +import type { ChatCompletion } from "openai/resources/index"; + +import type { AiModel, OredicAction, OredicPack, SystemPrompt } from "../config/routes"; +import type { AstNode, OredicMatches } from "./parsing"; +import type { TimerRes } from "./timer"; + +declare global { + namespace Express { + interface Request { + timerId: string; + endpoint?: string; + flags?: { + isLeveret?: boolean; + }; + ai?: { + model?: AiModel; + systemPrompt?: SystemPrompt; + }; + oredic?: { + pack?: OredicPack; + action?: OredicAction; + }; + } + } +} + +declare global { + namespace Express { + interface Response { + data: AiData | MembersData | OredicData; + timing: TimerRes; + } + } +} + +export type AiData = ChatCompletion; +export type MembersData = { + users?: Record; + ids?: string[]; + usernames?: string[]; +}; +export type OredicData = { + ast?: AstNode; + matches?: OredicMatches; + bestFilter?: { + ast: AstNode; + string: string; + }; +}; + +// Request type helpers for controllers +export type OredicRequestBody = { + filter?: string; + ast?: AstNode; + matches?: string[]; +}; + +export type OredicRequest = Request<{}, any, OredicRequestBody> & { + oredic: { + pack: OredicPack; + action: OredicAction; + }; +}; + +export function assertOredicRequest(req: Request): asserts req is OredicRequest { + const r = req as any; + if (!r.oredic?.pack || !r.oredic?.action) { + throw new Error("Invalid request: missing oredic context"); + } +} + +export {}; diff --git a/src/types/grok.ts b/src/types/grok.ts deleted file mode 100644 index c2c7ac5..0000000 --- a/src/types/grok.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { z } from "zod"; - -export const GrokInputDataSchema = z.object({ - userId: z.string().min(1, "userId cannot be empty"), - prompt: z - .string() - .min(1, "prompt cannot be empty") - .max(10000, "prompt too long"), - context: z.string().max(5000, "context too long"), - attachment: z.url("attachment must be a valid URL").nullable(), -}); -export type GrokInputData = z.infer; - -export type SystemPromptChoice = "default" | "hogichan" | "nomicord"; - -export type ModelChoice = "grok-3-mini" | "grok-4-0709"; - -const GrokTokenizeReqSchema = z.object({ - token_ids: z.array( - z.object({ - token_id: z.number(), - string_token: z.string(), - token_bytes: z.array(z.number()), - }) - ), -}); -export type GrokTokenizeReq = z.infer; diff --git a/src/types/parsing.ts b/src/types/parsing.ts new file mode 100644 index 0000000..be2fd7c --- /dev/null +++ b/src/types/parsing.ts @@ -0,0 +1,133 @@ +export enum OperatorChar { + AND = "&", + OR = "|", + XOR = "^", +} + +export enum SpecialChar { + WILDCARD = "*", + NEGATION = "!", + GROUP_START = "(", + GROUP_END = ")", + SPACE = " ", +} + +export enum LexemeNames { + OPERATOR = "operator", + WILDCARD = "wildcard", + NEGATION = "negation", + GROUP = "group", + TEXT = "text", +} + +export enum NodeNames { + OPERATOR = "operator", + PATTERN = "pattern", + REGEX = "regex", + OREDIC = "oredic", + TEXT = "text", + WILDCARD = "wildcard", +} + +export type AstNode = PatternNode | OperatorNode; +export type MatcherNode = RegexNode | OredicNode | MatcherOperatorNode | AstNode; + +export type OperatorNode = AndNode | OrNode | XorNode; +export type MatcherOperatorNode = MatcherAndNode | MatcherOrNode | MatcherXorNode; + +export type LexemeElement = TextLexeme | OperatorLexeme | NegationLexeme | WildcardLexeme | GroupLexeme; + +export type TextLexeme = { + type: LexemeNames.TEXT; + content: string; +}; + +export type OperatorLexeme = { + type: LexemeNames.OPERATOR; + content: "AND" | "OR" | "XOR"; +}; + +export type NegationLexeme = { + type: LexemeNames.NEGATION; + content: null; +}; + +export type WildcardLexeme = { + type: LexemeNames.WILDCARD; + content: null; +}; + +export type GroupLexeme = { + type: LexemeNames.GROUP; + content: LexemeElement[]; +}; + +export type LexicalParseResult = { + lexemes: LexemeElement[]; + consumed: number; +}; + +export type AndNode = { + type: NodeNames.OPERATOR; + operator: "AND"; + negation: boolean; + children: Array; +}; +export type OrNode = { + type: NodeNames.OPERATOR; + operator: "OR"; + negation: boolean; + children: Array; +}; +export type XorNode = { + type: NodeNames.OPERATOR; + operator: "XOR"; + negation: boolean; + children: Array; +}; + +export type MatcherAndNode = { + type: NodeNames.OPERATOR; + operator: "AND"; + negation: boolean; + children: Array; +}; +export type MatcherOrNode = { + type: NodeNames.OPERATOR; + operator: "OR"; + negation: boolean; + children: Array; +}; +export type MatcherXorNode = { + type: NodeNames.OPERATOR; + operator: "XOR"; + negation: boolean; + children: Array; +}; + +export type PatternNode = { + type: NodeNames.PATTERN; + negation: boolean; + children: Array; +}; + +export type RegexNode = { + type: NodeNames.REGEX; + negation: boolean; + rawChild: string; + child: RegExp; +}; + +export type OredicNode = { + type: NodeNames.OREDIC; + negation: boolean; + children: string[]; +}; + +export type Token = { type: NodeNames.TEXT; content: string } | { type: NodeNames.WILDCARD }; + +export type OredicMatches = string[]; + +export type BuildOptions = { + shortenedDisjunction: boolean; +}; diff --git a/src/types/server.ts b/src/types/server.ts index d56cd30..881fa5e 100644 --- a/src/types/server.ts +++ b/src/types/server.ts @@ -1,38 +1,13 @@ -import { SystemPromptChoice } from "./grok"; -import { EndpointConfig as ConfigEndpointConfig } from "../config/schema"; - -export type LogType = "success" | "info" | "warn" | "error" | "debug"; +export type LogType = "success" | "info" | "warn" | "error" | "debug" | "telemetry"; export type Filter = { - pattern: RegExp; - severity: "critical" | "high" | "medium" | "low"; - description: string; + pattern: RegExp; + severity: "critical" | "high" | "medium" | "low"; + description: string; }; export type PatternConfig = { - pattern: string; - severity: "critical" | "high" | "medium" | "low"; - description: string; -}; - -export type EndpointConfig = { - // /source/master/child - // /leveret/grok/nomicord - source: string; - main: string | null; - child: string | null; - config?: RequestEndpointConfig; + pattern: string; + severity: "critical" | "high" | "medium" | "low"; + description: string; }; - -export type RequestEndpointConfig = { - endpointName: string; - systemPrompt: SystemPromptChoice; - model: string; - maxPromptTokens: number; - maxContextTokens: number; - maxTotalTokens: number; - filters: string; - rateLimit: ConfigEndpointConfig["RATE_LIMIT"]; -}; - -export type EndpointLevel = "source" | "main" | "child"; diff --git a/src/types/time/timer.ts b/src/types/time/timer.ts new file mode 100644 index 0000000..6c05e43 --- /dev/null +++ b/src/types/time/timer.ts @@ -0,0 +1,7 @@ +export type TimerResult = { + raw: number; + adjusted: number; + formatted: string; +}; + +export type TimeUnit = "micro" | "ms" | "s" | "m"; diff --git a/src/types/timer.ts b/src/types/timer.ts new file mode 100644 index 0000000..120344f --- /dev/null +++ b/src/types/timer.ts @@ -0,0 +1,9 @@ +export type RawTime = number; +export type AdjustTime = number; +export type FormattedTime = string; + +export type TimerRes = { + raw: number; + adjusted: number; + formatted: string; +}; diff --git a/src/utils/Endpoint.ts b/src/utils/Endpoint.ts deleted file mode 100644 index 8ea5a24..0000000 --- a/src/utils/Endpoint.ts +++ /dev/null @@ -1,51 +0,0 @@ -let endpointHandler: EndpointHandler | null = null; - -export class EndpointHandler { - source: string; - main: string | null; - child: string | null; - - constructor(sourceIn: string) { - this.source = sourceIn; - this.main = null; - this.child = null; - } - - getSource() { - return this.source; - } - - getMain() { - return this.main; - } - - getChild() { - return this.child; - } - - setMain(mainIn: string) { - this.main = mainIn; - } - - setChild(childIn: string) { - this.child = childIn; - } -} - -export function initEndpointHandler(source: string): EndpointHandler { - if (endpointHandler) return endpointHandler; - endpointHandler = new EndpointHandler(source); - return endpointHandler; -} - -export function getEndpointHandler(): EndpointHandler { - if (!endpointHandler) - throw new Error( - "Endpoint Handler not initialized. Call initEndpointHandler(source:string) first." - ); - return endpointHandler; -} - -export function nullifyEndpointHandler() { - endpointHandler = null; -} diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts deleted file mode 100644 index 3130569..0000000 --- a/src/utils/Logger.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { config } from "../config/config"; -import { LogType } from "../types/server"; - -let logger: Logger | null = null; - -export class Logger { - name: string; - - constructor() { - this.name = config.LOGGER_NAME; - } - - static colors = { - success: "\x1b[32m", // Green - info: "\x1b[36m", // Cyan - warn: "\x1b[33m", // Yellow - error: "\x1b[31m", // Red - debug: "\x1b[35m", // Magenta - format: "\x1b[37m", // White - reset: "\x1b[0m", - }; - - simpleLog(type: LogType, message: string) { - const name = `[${this.name}:${type.toUpperCase()}]`; - const date = `[${this.getDateTime()}]`; - const whitespaces = " ".repeat("success".length - type.length); - const content = message; - const color = Logger.colors[type]; - const log = `${color}${name}${whitespaces}@${date}: ${content}${Logger.colors.reset}`; - console.log(log); - } - - formattingLog(title: string) { - const type = "format"; - const name = `[${this.name}:${type.toUpperCase()}]`; - const date = `[${this.getDateTime()}]`; - const whitespaces = " ".repeat("success".length - type.length); - const targetLength = 50; - const dashesNb = (targetLength - title.length) / 2; - const dashes = "-".repeat(dashesNb); - const extraDash = dashesNb % 1 != 0 ? "-" : ""; - const content = `|${dashes} ${title} ${dashes}${extraDash}|`; - const color = Logger.colors[type]; - const log = `${color}${name}${whitespaces}@${date}: ${content}${Logger.colors.reset}`; - console.log(log); - } - - getDateTime() { - const rawTimestamp = new Date(); - return rawTimestamp.toISOString(); - } -} - -export function initLogger(): Logger { - if (logger) return logger; - logger = new Logger(); - return logger; -} - -export function getLogger(): Logger { - if (!logger) - throw new Error("Logger not initialized. Call initLogger() first."); - return logger; -} diff --git a/src/utils/StringUtils.ts b/src/utils/StringUtils.ts new file mode 100644 index 0000000..ef3f848 --- /dev/null +++ b/src/utils/StringUtils.ts @@ -0,0 +1,71 @@ +import type fs from "fs"; + +export class StringUtils { + static getUtf8Bytes(string: string): number { + return new TextEncoder().encode(string).length; + } + + static toUtf8Decimals(string: string): number[] { + const out: number[] = []; + for (let i = 0; i < string.length; i++) { + const unicode = string.charCodeAt(i); + out.push(unicode); + } + return out; + } + + static isValidFileName(path: fs.PathLike): boolean { + const fileName = path.toString(); + + const byteLength = this.getUtf8Bytes(fileName); + if (byteLength > 255) { + return false; + } + + const unicodes: number[] = this.toUtf8Decimals(fileName); + for (const unicode of unicodes) { + if (unicode === 0 || unicode === 47) { + return false; + } + } + + if (unicodes.length === 0) return false; + + if (unicodes.length === 1 && unicodes[0] === 46) { + return false; + } + if (unicodes.length === 2 && unicodes[0] === 46 && unicodes[1] === 46) { + return false; + } + + return true; + } + + /* This enforces relative paths only, without dots to signify it is relative + * No paths beginning with / are allowed. + * This is good because npm always has some ambiguity as to where ./ is. + * This codebase makes all file operations through FileSystem, + * so paths should always be relative to the configured root directory. + */ + static isValidPath(path: fs.PathLike) { + let pathStr = path.toString(); + + if (pathStr.length === 0) return false; + + if (pathStr.startsWith("/")) return false; + + if (pathStr.endsWith("/")) { + pathStr = pathStr.substring(0, pathStr.length - 1); + } + + const elements = pathStr.split("/"); + + for (const element of elements) { + if (element === "") return false; + + if (!this.isValidFileName(element)) return false; + } + + return true; + } +} diff --git a/src/utils/Timer.ts b/src/utils/Timer.ts deleted file mode 100644 index db7df16..0000000 --- a/src/utils/Timer.ts +++ /dev/null @@ -1,27 +0,0 @@ -let timers: Map = new Map(); - -export class Timer { - startTime; - - constructor() { - this.startTime = Date.now(); - } - - getTime() { - return Date.now() - this.startTime; - } -} - -export function startTimer(id: string): void { - if (!timers.get(id)) timers.set(id, new Timer()); -} - -export function stopTimer(id: string): Timer { - const time = timers.get(id); - if (!time) - throw new Error( - `Timer ${id} not initialized. Call startTimer(id:string) first.` - ); - timers.delete(id); - return time; -} diff --git a/src/utils/bot/usernames.ts b/src/utils/bot/usernames.ts index 2f402b1..80e4032 100644 --- a/src/utils/bot/usernames.ts +++ b/src/utils/bot/usernames.ts @@ -1,15 +1,11 @@ -import { User } from "discord.js"; import { getDiscordClient } from "../../clients/DiscordClient"; -import { string } from "zod"; -export async function findDcIdByUsername( - dcUsername: string -): Promise { - const user = await getDiscordClient().client.users.fetch(dcUsername); - return user?.id ? user.id : null; +export async function findDcIdByUsername(dcUsername: string): Promise { + const user = await getDiscordClient().client.users.fetch(dcUsername); + return user?.id ? user.id : null; } export async function findDcUsernameById(dcId: string): Promise { - const user = await getDiscordClient().client.users.fetch(dcId); - return user?.username ? user.username : null; + const user = await getDiscordClient().client.users.fetch(dcId); + return user?.username ? user.username : null; } diff --git a/src/utils/grok/formatter.ts b/src/utils/grok/formatter.ts index c967593..1bbb779 100644 --- a/src/utils/grok/formatter.ts +++ b/src/utils/grok/formatter.ts @@ -1,47 +1,50 @@ -import { ChatCompletionCreateParamsNonStreaming } from "openai/resources/chat/completions"; -import { GrokController } from "../../controllers/GrokController"; -import { GrokInputData, SystemPromptChoice } from "../../types/grok"; -import { findDcUsernameById } from "../bot/usernames"; +import type { ChatCompletionCreateParamsNonStreaming } from "openai/resources/chat/completions"; + +import type { SystemPrompt } from "../../config/routes"; import { getDbHandler } from "../../db/DbHandler"; +import { DEFAULT_PROMPT, HOGICHAN_PROMPT, NOMICORD_PROMPT } from "../../loaders/storage"; +import type { AiInputData } from "../../types/ai"; + +const SYSTEM_PROMPTS = { + default: DEFAULT_PROMPT, + hogichan: HOGICHAN_PROMPT, + nomicord: NOMICORD_PROMPT, +} as const; -export function formatQuestion( - discordUsername: string, - rawQuestion: string, - rawContext: string -) { - const prefix = `Question by :`; - const question = filter(rawQuestion, discordUsername); - const context = filter(rawContext, discordUsername); - const body = `Context:\n${context}\n\nQuestion:\n${question}`; - return `${prefix}\n${body}`; +export function formatQuestion(discordUsername: string, rawQuestion: string, rawContext: string) { + const prefix = `Question by :`; + const question = filter(rawQuestion, discordUsername); + const context = filter(rawContext, discordUsername); + const body = `Context:\n${context}\n\nQuestion:\n${question}`; + return `${prefix}\n${body}`; } function filter(text: string, discordUsername: string) { - return text; + return text; } export async function formatCompletion( - reqBody: GrokInputData, - model: string, - type: SystemPromptChoice, - endpointName: string + reqBody: AiInputData, + model: string, + type: SystemPrompt, + modelName: string, ): Promise { - const completion: ChatCompletionCreateParamsNonStreaming = { - model: model, - messages: [ - { - role: "system", - content: GrokController.systemPromptMappings[type], - }, - { - role: "user", - content: formatQuestion( - await getDbHandler().getUsername(reqBody.userId, endpointName), - reqBody.prompt, - reqBody.context - ), - }, - ], - }; - return completion; + const completion: ChatCompletionCreateParamsNonStreaming = { + model: model, + messages: [ + { + role: "system", + content: SYSTEM_PROMPTS[type], + }, + { + role: "user", + content: formatQuestion( + await getDbHandler().getUsername(reqBody.userId, modelName), + reqBody.prompt, + reqBody.context, + ), + }, + ], + }; + return completion; } diff --git a/src/utils/grok/grok.ts b/src/utils/grok/grok.ts index bffad84..3b30917 100644 --- a/src/utils/grok/grok.ts +++ b/src/utils/grok/grok.ts @@ -1,48 +1,42 @@ import { getGrokClient } from "../../clients/GrokClient"; import { env } from "../../config/config"; -import { GrokTokenizeReq } from "../../types/grok"; -import { getLogger } from "../Logger"; +import { getLogger } from "../../helpers/Logger"; +import type { GrokTokenizeReq } from "../../types/ai"; export async function tokenizeGrok( - text: string, - model: string + text: string, + model: string, ): Promise<{ stringTokens: string[]; tokenCount: number } | null> { - const response = await makeTokenizeReq(text, model); - if (!response) { - return null; - } - return { - stringTokens: response.token_ids.map((token) => token.string_token), - tokenCount: response.token_ids.length, - }; + const response = await makeTokenizeReq(text, model); + if (!response) { + return null; + } + return { + stringTokens: response.token_ids.map((token) => token.string_token), + tokenCount: response.token_ids.length, + }; } -async function makeTokenizeReq( - text: string, - model: string -): Promise { - const response = await fetch( - getGrokClient().client.baseURL + "/tokenize-text", - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${env.GROK_API_KEY}`, - }, - body: JSON.stringify({ - model, - text: text, - }), - } - ); +async function makeTokenizeReq(text: string, model: string): Promise { + const response = await fetch(getGrokClient().client.baseURL + "/tokenize-text", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${env.GROK_API_KEY}`, + }, + body: JSON.stringify({ + model, + text: text, + }), + }); - if (!response.ok) { - getLogger().simpleLog( - "error", - `Error tokenizing input with grok. ${response.status} ${response.statusText}` - ); - return null; - } + if (!response.ok) { + getLogger().simpleLog( + "error", + `Error tokenizing input with grok. ${response.status} ${response.statusText}`, + ); + return null; + } - return await response.json(); + return response.json(); } diff --git a/src/utils/parentClasses/ErrorProne.ts b/src/utils/parentClasses/ErrorProne.ts new file mode 100644 index 0000000..bf65ff3 --- /dev/null +++ b/src/utils/parentClasses/ErrorProne.ts @@ -0,0 +1,86 @@ +import type { StandardError } from "../../types/errors"; + +export class ErrorProne { + protected error: StandardError; + protected className: string; + + constructor(className?: string) { + this.className = className || this.constructor.name; + this.error = { + type: "error", + code: null, + status: false, + send: false, + message: null, + location: __dirname, + time: null, + context: null, + stackTrace: [], + }; + } + + protected sendErrorToClient() { + return { + message: this.error.message ? this.error.message : "no message", + culprit: `Error in file: ${this.error.location}, from method: ${this.error.context ? this.error.context : "Uknown"}`, + time: this.error.time ? this.error.time : "Uknown", + stackTrace: this.error.stackTrace, + }; + } + + protected setError(code: number, message: string, methodName?: string): StandardError { + this.error.code = code; + this.error.status = true; + this.error.send = true; + this.error.message = message; + this.error.time = new Date(); + this.error.context = methodName ? `${this.className}.${methodName}` : this.className; + this.error.stackTrace = []; + + return this.error; + } + + protected setWarn(message: string, methodName?: string): StandardError { + this.error.code = 200; + this.error.status = true; + this.error.send = true; + this.error.message = message; + this.error.time = new Date(); + this.error.context = methodName ? `${this.className}.${methodName}` : this.className; + this.error.stackTrace = []; + + return this.error; + } + + protected propagateError(childError: StandardError, message: string, methodName?: string): StandardError { + const context = methodName ? `${this.className}.${methodName}` : this.className; + + return { + type: "error", + code: childError.code, + status: true, + send: true, + message: message, + location: __dirname, + time: new Date(), + context: context, + stackTrace: [childError, ...(childError.stackTrace || [])], + }; + } + + protected getErrorCode() { + const errorCode = this.error.code; + if (!errorCode) { + this.propagateError( + this.error, + "Could not even get error code... somehow? Defaulting to 500. If you see this, the API is fucked.", + ); + return 500; + } + return errorCode; + } + + protected isError(value: any): value is StandardError { + return value && typeof value === "object" && value.type === "error"; + } +} diff --git a/src/utils/parsers/OredicBuilder.ts b/src/utils/parsers/OredicBuilder.ts new file mode 100644 index 0000000..3365e1c --- /dev/null +++ b/src/utils/parsers/OredicBuilder.ts @@ -0,0 +1,142 @@ +import type { OredicPack } from "../../config/routes"; +import type { StandardError } from "../../types/errors"; +import type { AstNode, BuildOptions, OredicMatches, OrNode, PatternNode } from "../../types/parsing"; +import { NodeNames, OperatorChar } from "../../types/parsing"; +import { ErrorProne } from "../parentClasses/ErrorProne"; +import { OredicParser } from "./OredicParser"; +import { OredicShortener } from "./OredicShortener"; +export class OredicBuilder extends ErrorProne { + private shortener: OredicShortener; + private parser: OredicParser; + private shortcuts: Map; + + constructor(private pack: OredicPack) { + super("OredicBuilder"); + + this.shortener = new OredicShortener(pack); + this.parser = new OredicParser(pack); + + const shortcuts = this.shortener.getShortcuts(); + if (this.isError(shortcuts)) { + throw new Error(`Failed to load shortcuts: ${shortcuts.message}`); + } + this.shortcuts = shortcuts; + } + + buildFromMatches(matches: OredicMatches, options: BuildOptions): AstNode[] | StandardError { + const candidates = new Set(); + const pipeline = this.buildPipeline(options); + + for (const buildStrategy of pipeline) { + const result = buildStrategy(matches); + + if (this.isError(result)) { + return this.propagateError(result, "Build strategy failed", "buildFromMatches"); + } + if (result) { + candidates.add(result); + } + } + + if (candidates.size === 0) { + return this.setError(500, "No valid AST candidates generated", "buildFromMatches"); + } + return Array.from(candidates); + } + + buildFromAst(ast: AstNode): string | StandardError { + if (!ast) { + return ""; + } + + switch (ast.type) { + case NodeNames.OPERATOR: + const char: OperatorChar = this.getOperatorChar(ast.operator); + const children: string[] = []; + + for (let i = 0; i < ast.children.length; i++) { + const child: AstNode = ast.children[i]; + let subFilter = this.buildFromAst(child); + + if (typeof subFilter !== "string") return subFilter; + + if (child.type === NodeNames.OPERATOR) { + const needsParens = i > 0 && child.operator !== ast.operator; + if (needsParens) { + subFilter = `(${subFilter})`; + } + } + + if (child.negation) { + subFilter = `!${subFilter}`; + } + + children.push(subFilter); + } + return children.join(char); + + case NodeNames.PATTERN: + return ast.children + .map((token) => { + if (token.type === NodeNames.WILDCARD) { + return "*"; + } else if (token.type === NodeNames.TEXT) { + return token.content; + } + return ""; + }) + .join(""); + } + } + + private shortenedDisjuntion(matches: OredicMatches): OrNode | StandardError { + const children: PatternNode[] = []; + for (const match in matches) { + const candidate = this.shortcuts.get(match); + const rawPattern = candidate ? candidate : match; + const nodeCandidate = this.parser.parse(rawPattern); + + if (!nodeCandidate) { + continue; + } + if (this.isError(nodeCandidate)) { + return this.propagateError(nodeCandidate, "Failed to parse pattern", "shortenedDisjuntion"); + } + if (nodeCandidate.type === NodeNames.OPERATOR) { + return this.setError(500, "Parsed a pattern as an operator", "shortenedDisjuntion"); + } + + children.push(nodeCandidate); + } + + return { + type: NodeNames.OPERATOR, + operator: "OR", + negation: false, + children: children, + }; + } + + private buildPipeline(options: BuildOptions) { + const pipeline = []; + + if (options.shortenedDisjunction) { + pipeline.push(this.shortenedDisjuntion.bind(this)); + } + + return pipeline; + } + + private getOperatorChar(string: "AND" | "OR" | "XOR"): OperatorChar { + switch (string) { + case "AND": + return OperatorChar.AND; + case "OR": + return OperatorChar.OR; + case "XOR": + return OperatorChar.XOR; + default: + throw new Error(`Invalid operator string: ${string}`); + } + } +} diff --git a/src/utils/parsers/OredicMatcher.ts b/src/utils/parsers/OredicMatcher.ts new file mode 100644 index 0000000..4493ef7 --- /dev/null +++ b/src/utils/parsers/OredicMatcher.ts @@ -0,0 +1,353 @@ +import type { OredicPack } from "../../config/routes"; +import { DUMPS } from "../../loaders/storage"; +import type { StandardError } from "../../types/errors"; +import type { + AstNode, + MatcherAndNode, + MatcherNode, + MatcherOrNode, + MatcherXorNode, + OredicMatches, + OredicNode, + PatternNode, + RegexNode, +} from "../../types/parsing"; +import { NodeNames } from "../../types/parsing"; +import { ErrorProne } from "../parentClasses/ErrorProne"; +import { OredicParser } from "./OredicParser"; + +const EMPTY_OREDIC_NODE: OredicNode = { + type: NodeNames.OREDIC, + negation: false, + children: [], +} as const; + +export class OredicMatcher extends ErrorProne { + private parser: OredicParser; + private dump: string[]; + + constructor(private pack: OredicPack) { + super("OredicMatcher"); + this.parser = new OredicParser(pack); + const dump = DUMPS.get(this.pack); + this.dump = dump ? dump.split("\n").filter((line) => line.trim() !== "") : []; + } + + match(rules: AstNode): OredicMatches | StandardError { + const result = this.parse(rules); + if (result === null) { + return this.setError(500, "Failed to match oredics", "match"); + } + return result; + } + + isUniqueMatch(pattern: string, targetIndex: number): boolean { + if (targetIndex < 0 || targetIndex >= this.dump.length) { + return false; + } + + const target = this.dump[targetIndex]; + const regex = this.buildRegexFromPattern(pattern); + + if (!regex.test(target)) { + return false; + } + + const maxRadius = 128; + for (let radius = 1; radius <= maxRadius; radius *= 2) { + for (let offset = -radius; offset <= radius; offset++) { + const checkIndex = targetIndex + offset; + + if (checkIndex === targetIndex || checkIndex < 0 || checkIndex >= this.dump.length) { + continue; + } + + regex.lastIndex = 0; + if (regex.test(this.dump[checkIndex])) { + return false; + } + } + } + + for (let i = 0; i < this.dump.length; i++) { + const distance = Math.abs(i - targetIndex); + + if (distance <= maxRadius || i === targetIndex) { + continue; + } + + regex.lastIndex = 0; + if (regex.test(this.dump[i])) { + return false; + } + } + + return true; + } + + /* + * AST transformation pipeline + */ + + private parse(ast: MatcherNode | null): string[] | null { + this.parsePatterns(ast); + this.parseRegexes(ast); + const finalNode = this.parseOredics(ast); + if (!finalNode) return null; + return finalNode.children; + } + + private parsePatterns(ast: MatcherNode | null): void { + if (!ast || ast.type === "regex" || ast.type === "oredic") return; + + if (ast.type === "pattern") { + const regexNode = this.createRegex(ast); + delete (ast as any).children; + Object.assign(ast, regexNode); + return; + } + + for (const child of ast.children) { + this.parsePatterns(child); + } + } + + private parseRegexes(ast: MatcherNode | null): void { + if (!ast || ast.type === "pattern" || ast.type === "oredic") return; + + if (ast.type === "regex") { + const oredicNode = this.matchOredics(ast); + + if (!oredicNode) return; // Error + + delete (ast as any).children; + delete (ast as any).child; + delete (ast as any).rawChild; + Object.assign(ast, oredicNode); + return; + } + + for (const child of ast.children) { + this.parseRegexes(child); + } + } + + private parseOredics(ast: MatcherNode | null): OredicNode | null { + if (!ast) return null; + + if (ast.type === "oredic") return ast; + + if (ast.type !== "operator") { + this.setError( + 500, + "Unknown Error: Got something else than Oredic and Operator Nodes in the final parse", + "parseOredics", + ); + return null; + } + + if (ast.negation && (ast.operator === "AND" || ast.operator === "OR")) { + ast.operator = ast.operator === "AND" ? "OR" : "AND"; + ast.negation = false; + for (const child of ast.children) { + child.negation = !child.negation; + } + } + + for (let i = 0; i < ast.children.length; i++) { + const result = this.parseOredics(ast.children[i]); + if (result) { + ast.children[i] = result; + } + } + + let newNode: OredicNode | null = null; + switch (ast.operator) { + case "AND": + newNode = this.applyConjunction(ast); + break; + case "OR": + newNode = this.applyDisjunction(ast); + break; + case "XOR": + newNode = this.applyExclusiveDisjunction(ast); + break; + } + + if (newNode) { + delete (ast as any).operator; + Object.assign(ast, newNode); + return ast as unknown as OredicNode; + } + + return null; + } + + private createRegex(node: PatternNode): RegexNode { + let pattern = "^"; + for (const child of node.children) { + if (child.type === "wildcard") { + pattern += "[a-zA-Z]+"; + } else { + pattern += child.content; + } + } + pattern += "$"; + return { + type: NodeNames.REGEX, + negation: node.negation, + rawChild: pattern, + child: new RegExp(pattern, "gm"), + }; + } + + private matchOredics(node: RegexNode): OredicNode | null { + const dump = DUMPS.get(this.pack); + if (!dump) { + return null; + // Error + } + const oredicList = dump.match(node.child); + return this.newOredicNode(oredicList); + } + + private applyConjunction(node: MatcherAndNode): OredicNode { + const occurences = new Map(); + const toRemove = new Set(); + const newChildren: string[] = []; + let nonNegatedCount = 0; + + for (const child of node.children) { + if (child.type !== "oredic") { + this.setError( + 500, + "Unknown Error: Tried to apply conjunction on something else than an oredic node", + "applyConjunction", + ); + return this.newOredicNode(null); + } + + if (!child.children || child.children.length === 0) { + if (!child.negation) { + return this.newOredicNode(null); + } + continue; + } + + if (child.negation) { + for (const oredic of child.children) { + toRemove.add(oredic); + } + } else { + nonNegatedCount++; + for (const oredic of child.children) { + const currentCount = occurences.get(oredic) ?? 0; + occurences.set(oredic, currentCount + 1); + } + } + } + + if (nonNegatedCount === 0) { + this.setError(500, "AND operation requires at least one non-negated child", "applyConjunction"); + return this.newOredicNode(null); + } + + occurences.forEach((value, key) => { + if (value === nonNegatedCount && !toRemove.has(key)) { + newChildren.push(key); + } + }); + + return this.newOredicNode(newChildren); + } + + private applyDisjunction(node: MatcherOrNode): OredicNode { + let newChildren: string[] = []; + + for (const child of node.children) { + if (child.type !== "oredic") { + this.setError( + 500, + "Unknown Error: Tried to apply disjunction on something else than an oredic node", + "applyDisjunction", + ); + return this.newOredicNode(null); + } + + if (!child.negation) { + const toConcat = child.children ? child.children : []; + newChildren = newChildren.concat(toConcat); + } + } + + return this.newOredicNode(newChildren); + } + + private applyExclusiveDisjunction(node: MatcherXorNode): OredicNode { + const occurences = new Map(); + let newChildren: string[] = []; + + for (const child of node.children) { + if (child.type !== "oredic") { + this.setError( + 500, + "Unknown Error: Tried to apply xor on something else than an oredic node", + "applyExclusiveDisjunction", + ); + return this.newOredicNode(null); + } + + if (!child.children || child.children.length === 0) { + continue; + } + + const multiplier = child.negation ? -1 : 1; + + for (const oredic of child.children) { + const currentCount = occurences.get(oredic) ?? 0; + occurences.set(oredic, currentCount + multiplier); + } + } + + occurences.forEach((value, key) => { + if (Math.abs(value) % 2 === 1) { + newChildren.push(key); + } + }); + + if (node.negation) { + const dump = DUMPS.get(this.pack); + if (!dump) { + this.setError(500, "Failed to get dump for negated XOR operation"); + return this.newOredicNode(null); + } + + const allOredics = dump.split("\n").filter((line) => line.trim() !== ""); + const resultSet = new Set(newChildren); + newChildren = allOredics.filter((oredic) => !resultSet.has(oredic)); + } + + return this.newOredicNode(newChildren); + } + + private buildRegexFromPattern(pattern: string): RegExp { + let regexPattern = "^"; + for (let i = 0; i < pattern.length; i++) { + if (pattern[i] === "*") { + regexPattern += ".+"; + } else { + regexPattern += pattern[i]; + } + } + regexPattern += "$"; + return new RegExp(regexPattern); + } + + private newOredicNode(children: string[] | null): OredicNode { + return { + type: NodeNames.OREDIC, + negation: false, + children: children ? children : [], + }; + } +} diff --git a/src/utils/parsers/OredicParser.ts b/src/utils/parsers/OredicParser.ts new file mode 100644 index 0000000..a9113ac --- /dev/null +++ b/src/utils/parsers/OredicParser.ts @@ -0,0 +1,539 @@ +/* + * Meant to imitate the logic in + * https://github.com/AE2-UEL/Applied-Energistics-2/blob/26bb5986c636e9bdde62559b0d1c2bbc48c4b9e3/src/main/java/appeng/util/item/OreDictFilterMatcher.java#L11 + */ + +import type { OredicPack } from "../../config/routes"; +import type { StandardError } from "../../types/errors"; +import type { AstNode, LexemeElement, OperatorNode, PatternNode, Token } from "../../types/parsing"; +import { LexemeNames, NodeNames, OperatorChar, SpecialChar } from "../../types/parsing"; +import { ErrorProne } from "../parentClasses/ErrorProne"; + +type ParseState = { + ast: AstNode | null; + tokenBuffer: Token[]; + operandBuffer: AstNode[]; + currentOperator: "AND" | "XOR" | "OR" | null; + negationFlag: boolean; +}; + +export class OredicParser extends ErrorProne { + private lexemeBuffer: string; + private parenthesesCount: number; + private optimizationPipeline: Array<(node: AstNode) => void>; + + constructor(private pack: OredicPack) { + super("OredicParser"); + this.lexemeBuffer = ""; + this.parenthesesCount = 0; + + this.optimizationPipeline = [ + this.applyAssociativity.bind(this), + this.applyAnnihilatorXor.bind(this), + this.applyDeMorgan.bind(this), + this.applyWildcardConjunction.bind(this), + ]; + } + + parse(input: string): AstNode | StandardError { + this.resetState(); + + const { lexemeList } = this.lexicalParse(input.split("")); + + if (this.parenthesesCount !== 0) { + return this.setError(400, "Unbalanced parentheses", "parse"); + } + + const ast = this.parseNode(lexemeList); + + if (this.isError(ast)) { + return this.propagateError(ast, "Failed to parse node", "parse"); + } + + if (!ast) { + return this.setError(400, "Failed to parse input", "parse"); + } + + if (this.error.status) { + return this.error; + } + + this.flattenAst(ast); + + if (this.error.status) { + return this.error; + } + + return ast; + } + + /* + * State management + */ + + private resetState(): void { + this.lexemeBuffer = ""; + this.parenthesesCount = 0; + this.error.status = false; + this.error.code = null; + this.error.message = null; + } + + /* + * Lexical parsing + */ + + private lexicalParse(pattern: string[]): { + lexemeList: LexemeElement[]; + consumed: number; + } { + let lexemeList: LexemeElement[] = []; + + for (let i = 0; i < pattern.length; i++) { + switch (pattern[i]) { + case SpecialChar.SPACE: + break; + + case SpecialChar.GROUP_START: + lexemeList = this.flushLexemeBuffer(lexemeList); + this.incrementParentheses(); + + const result = this.lexicalParse(pattern.slice(i + 1)); + lexemeList.push({ + type: LexemeNames.GROUP, + content: result.lexemeList, + }); + + i += result.consumed; + + break; + case SpecialChar.GROUP_END: + lexemeList = this.flushLexemeBuffer(lexemeList); + this.decrementParentheses(); + return { lexemeList, consumed: i + 1 }; + + case OperatorChar.AND: + lexemeList = this.flushLexemeBuffer(lexemeList); + lexemeList.push({ type: LexemeNames.OPERATOR, content: "AND" }); + break; + case OperatorChar.XOR: + lexemeList = this.flushLexemeBuffer(lexemeList); + lexemeList.push({ type: LexemeNames.OPERATOR, content: "XOR" }); + break; + case OperatorChar.OR: + lexemeList = this.flushLexemeBuffer(lexemeList); + lexemeList.push({ type: LexemeNames.OPERATOR, content: "OR" }); + break; + + case SpecialChar.NEGATION: + lexemeList = this.flushLexemeBuffer(lexemeList); + lexemeList.push({ type: LexemeNames.NEGATION, content: null }); + break; + + case SpecialChar.WILDCARD: + lexemeList = this.flushLexemeBuffer(lexemeList); + lexemeList.push({ type: LexemeNames.WILDCARD, content: null }); + break; + + default: + this.lexemeBuffer += pattern[i]; + break; + } + } + + this.flushLexemeBuffer(lexemeList); + return { lexemeList, consumed: pattern.length }; + } + + private parseNode(lexemeList: LexemeElement[]): AstNode | null { + if (this.error.status) { + return null; + } + + const state: ParseState = { + ast: null, + tokenBuffer: [], + operandBuffer: [], + currentOperator: null, + negationFlag: false, + }; + + for (let i = 0; i < lexemeList.length; i++) { + switch (lexemeList[i].type) { + case "group": + const subLexeme = lexemeList[i].content; + if (typeof subLexeme === "string" || !subLexeme) { + this.setError(400, "Invalid group content", "parseNode"); + return null; + } + const subAst: AstNode | null = this.parseNode(subLexeme); + + if (this.error.status) { + return null; + } + + if (!subAst) { + this.setError(400, "Failed to parse group", "parseNode"); + return null; + } + + if (state.negationFlag) { + subAst.negation = true; + state.negationFlag = false; + } + + if (!state.ast) { + state.ast = subAst; + break; + } + + state.operandBuffer.push(subAst); + break; + + case "operator": + const operator = lexemeList[i].content; + if (operator !== "AND" && operator !== "OR" && operator !== "XOR") { + this.setError(400, "Invalid operator", "parseNode"); + return null; + } + + this.flushTokens(state); + + if (state.operandBuffer.length > 0 && state.currentOperator) { + this.flushOperands(state, state.currentOperator); + } + + state.currentOperator = operator; + break; + + case "negation": + if (state.tokenBuffer.length !== 0) { + this.setError(400, "Negation must come before pattern tokens", "parseNode"); + return null; + } + state.negationFlag = !state.negationFlag; + break; + + case "wildcard": + state.tokenBuffer.push({ type: NodeNames.WILDCARD }); + break; + + case "text": + const content = lexemeList[i].content; + if (typeof content !== "string") { + this.setError(400, "Invalid text content", "parseNode"); + return null; + } + state.tokenBuffer.push({ type: NodeNames.TEXT, content: content }); + break; + } + } + + return this.flushEnd(state); + } + + private flattenAst(ast: AstNode): void { + if (!ast) { + return; + } + + const currentAst = ast; + let previousAst: AstNode | null = null; + + while (JSON.stringify(currentAst) !== JSON.stringify(previousAst)) { + previousAst = JSON.parse(JSON.stringify(currentAst)); + + for (const fn of this.optimizationPipeline) { + fn(currentAst); + } + + if (currentAst.type === NodeNames.PATTERN) return; + + for (const child of currentAst.children) { + this.flattenAst(child); + } + + if (this.error.status) return; + } + } + + /* + * Buffer management + */ + + private flushLexemeBuffer(lexemeList: LexemeElement[]): LexemeElement[] { + if (this.lexemeBuffer === "") { + return lexemeList; + } + lexemeList.push({ type: LexemeNames.TEXT, content: this.lexemeBuffer }); + this.lexemeBuffer = ""; + return lexemeList; + } + + private flushTokens(state: ParseState): void { + if (state.tokenBuffer.length > 0) { + state.operandBuffer.push(this.createPatternNode(state.tokenBuffer, state.negationFlag)); + state.tokenBuffer = []; + state.negationFlag = false; + } + } + + private flushOperands(state: ParseState, operator: "AND" | "XOR" | "OR"): void { + if (state.operandBuffer.length === 0) { + return; + } + + if (!state.ast) { + if (state.operandBuffer.length === 1) { + state.ast = state.operandBuffer[0]; + state.operandBuffer = []; + return; + } + + state.ast = { + type: NodeNames.OPERATOR, + operator: operator, + negation: false, + children: state.operandBuffer, + }; + state.operandBuffer = []; + return; + } + + if (state.ast.type === NodeNames.OPERATOR && state.ast.operator === operator) { + state.ast.children = state.ast.children.concat(state.operandBuffer); + state.operandBuffer = []; + return; + } + + state.ast = { + type: NodeNames.OPERATOR, + operator: operator, + negation: false, + children: [state.ast].concat(state.operandBuffer), + }; + state.operandBuffer = []; + } + + private flushEnd(state: ParseState): AstNode | null { + if (state.tokenBuffer.length > 0) { + const pattern = this.createPatternNode(state.tokenBuffer, state.negationFlag); + + if (!state.ast && !state.currentOperator) { + return pattern; + } + + state.operandBuffer.push(pattern); + } + + if (state.operandBuffer.length > 0 && state.currentOperator) { + this.flushOperands(state, state.currentOperator); + } + + return state.ast; + } + + /* + * Character classification and operator helpers + */ + + private isOperatorChar(char: string): boolean { + return char === OperatorChar.AND || char === OperatorChar.OR || char === OperatorChar.XOR; + } + + private getOperatorName(char: string): "AND" | "OR" | "XOR" { + switch (char) { + case OperatorChar.AND: + return "AND"; + case OperatorChar.OR: + return "OR"; + case OperatorChar.XOR: + return "XOR"; + default: + throw new Error(`Invalid operator character: ${char}`); + } + } + + private incrementParentheses(): void { + this.parenthesesCount += 1; + } + + private decrementParentheses(): void { + if (this.parenthesesCount === 0) { + this.setError(400, "Closing parenthesis without opening", "decrementParentheses"); + return; + } + this.parenthesesCount -= 1; + } + + private createPatternNode(children: Token[], negation: boolean = false): PatternNode { + const node: PatternNode = { + type: NodeNames.PATTERN, + negation: negation, + children: children, + }; + return node; + } + + /* + * Optimization methods + */ + + // Associativity (OR, AND, XOR): OR: (a, OR: (b, c)) = OR: (a, b, c) + private applyAssociativity(node: AstNode): void { + if (!this.operatorAccepted(node, "all")) return; + const operatorNode = node as OperatorNode; + + const topLevelOperator = operatorNode.operator; + const newChildren: AstNode[] = []; + + for (let i = 0; i < operatorNode.children.length; i++) { + const child = operatorNode.children[i]; + if ( + child.type === NodeNames.OPERATOR && + child.operator === topLevelOperator && + child.negation === false + ) { + for (const subChild of child.children) { + newChildren.push(subChild); + } + } else { + newChildren.push(child); + } + } + + node.children = newChildren; + } + + // Distributivity of AND over OR: OR: (AND: (a, b), AND: (a, c)) = AND: (a, OR: (b, c)) + // Distributivity of OR over AND: AND: (OR: (a, b), OR: (a, c)) = OR: (a, AND: (b, c)) + private applyDistributivity(node: AstNode): void { + if (!this.operatorAccepted(node, ["AND", "OR"])) return; + const operatorNode = node as OperatorNode; + } + + // Identity for OR: OR: (a, false) = a + private applyIdentityOr(node: AstNode): void { + if (!this.operatorAccepted(node, ["OR"])) return; + const operatorNode = node as OperatorNode; + } + + // Identity for AND: AND: (a, true) = a + private applyIdentityAnd(node: AstNode): void { + if (!this.operatorAccepted(node, ["AND"])) return; + const operatorNode = node as OperatorNode; + } + + // Identity for XOR: XOR: (a, false) = a + private applyIdentityXor(node: AstNode): void { + if (!this.operatorAccepted(node, ["XOR"])) return; + const operatorNode = node as OperatorNode; + } + + // Annihilator for OR: OR: (a, true) = true + private applyAnnihilatorOr(node: AstNode): void { + if (!this.operatorAccepted(node, ["OR"])) return; + const operatorNode = node as OperatorNode; + } + + // Annihilator for AND: AND: (a, false) = false => error + private applyAnnihilatorAnd(node: AstNode): void { + if (!this.operatorAccepted(node, ["AND"])) return; + const operatorNode = node as OperatorNode; + } + + // Annihilator for XOR: XOR: (a, a) = false => error + private applyAnnihilatorXor(node: AstNode): void { + if (!this.operatorAccepted(node, ["XOR"])) return; + const operatorNode = node as OperatorNode; + + const uniqueChildren = new Map(); + + for (const child of operatorNode.children) { + const hash = JSON.stringify(child); + if (uniqueChildren.has(hash)) { + this.setWarn("Invalid Logic: XOR has two identical elements. XOR(a,a) is always false"); + return; + } + uniqueChildren.set(hash, true); + } + } + + // Idempotence (OR, AND): OR: (a, a) = a + private applyIdempotence(node: AstNode): void { + if (!this.operatorAccepted(node, ["AND", "OR"])) return; + const operatorNode = node as OperatorNode; + } + + // Absorption (OR, AND): AND: (a, OR: (a, b)) = a + private applyAbsorption(node: AstNode): void { + if (!this.operatorAccepted(node, ["AND", "OR"])) return; + const operatorNode = node as OperatorNode; + } + + // Complementation for AND: AND: (a, NOT(a)) = false => error + private applyComplementationAnd(node: AstNode): void { + if (!this.operatorAccepted(node, ["AND"])) return; + const operatorNode = node as OperatorNode; + } + + // Complementation for OR: OR: (a, NOT(a)) = true + private applyComplementationOr(node: AstNode): void { + if (!this.operatorAccepted(node, ["OR"])) return; + const operatorNode = node as OperatorNode; + } + + // Complementary for XOR: XOR(a, true) = NOT(a), XOR(a, NOT(a)) = true + // Complementation for OR: OR: (a, NOT(a)) = true + private applyComplementaryOrs(node: AstNode): void { + if (!this.operatorAccepted(node, ["XOR"])) return; + const operatorNode = node as OperatorNode; + } + + // Double negation: NOT(NOT(a)) = a + private applyDoubleNegation(node: AstNode): void { + if (!this.operatorAccepted(node, "all")) return; + const operatorNode = node as OperatorNode; + } + + // De Morgan's laws: OR: (NOT(a), NOT(b)) = NOT(AND: (a, b)) + private applyDeMorgan(node: AstNode): void { + if (!this.operatorAccepted(node, ["AND", "OR"])) return; + const operatorNode = node as OperatorNode; + + let allNegation = true; + + for (const child of operatorNode.children) { + if (!child.negation) allNegation = false; + } + + if (allNegation) { + const complemetary = operatorNode.operator === "OR" ? "AND" : "OR"; + operatorNode.negation = true; + operatorNode.operator = complemetary; + operatorNode.children.forEach((child) => { + child.negation = false; + }); + } + + node = operatorNode; + } + + private applyWildcardConjunction(node: AstNode): void { + if (node.type !== NodeNames.PATTERN) return; + for (let i = 1; i < node.children.length; i++) { + const lastChild = node.children[i - 1]; + const currChild = node.children[i]; + if (currChild.type === NodeNames.WILDCARD && lastChild.type === NodeNames.WILDCARD) { + node.children.splice(i, 1); + i -= 1; + } + } + } + + private operatorAccepted(node: AstNode, acceptedOperators: Array<"AND" | "OR" | "XOR"> | "all"): boolean { + if (node.type === NodeNames.PATTERN) return false; + if (acceptedOperators === "all") return true; + return acceptedOperators.includes(node.operator); + } +} diff --git a/src/utils/parsers/OredicShortener.ts b/src/utils/parsers/OredicShortener.ts new file mode 100644 index 0000000..1591cb8 --- /dev/null +++ b/src/utils/parsers/OredicShortener.ts @@ -0,0 +1,179 @@ +import type { OredicPack } from "../../config/routes"; +import { getLogger } from "../../helpers/Logger"; +import { startTimer, stopTimer, Timer } from "../../helpers/Timer"; +import { DUMPS } from "../../loaders/storage"; +import type { StandardError } from "../../types/errors"; +import type { TimerRes } from "../../types/timer"; +import { ErrorProne } from "../parentClasses/ErrorProne"; +import { OredicMatcher } from "./OredicMatcher"; + +const PROGRESS_UPDATE_INTERVAL = 10; + +type TelemetryResults = { + result: { + string: string; + length: number; + shortening: number; + }; + timings: TimerRes; +}; + +export class OredicShortener extends ErrorProne { + private dump: string[]; + private matcher: OredicMatcher; + private telemetry: Map; + private shortcuts: Map; + + constructor(private pack: OredicPack) { + super("OredicShortener"); + this.dump = this.loadDump(); + this.matcher = new OredicMatcher(this.pack); + this.telemetry = new Map(); + this.shortcuts = new Map(); + } + + initShortcuts() { + const shortcuts = this.getShortcuts(); + if (this.isError(shortcuts)) { + return this.propagateError(shortcuts, "Could not initialize shortcuts", "initShortcuts"); + } + if (this.shortcuts.size === 0) { + return this.setError(500, "Could not initialize shortcuts", "initShortcuts"); + } + } + + getShortcuts(): Map | StandardError { + //TODO: Implement caching strategy!!! + + if (this.shortcuts.size === 0) { + const result = this.findAll(); + if (this.isError(result)) { + return this.propagateError(result, "Failed to generate shortcuts", "getShortcuts"); + } + this.shortcuts = result; + } + + return this.shortcuts; + } + + getTelemetry(): Map { + return new Map(this.telemetry); + } + + private findAll(): Map { + getLogger().simpleLog("info", `Finding shortest patterns for ${this.pack}`); + startTimer("shortening"); + + const result = new Map(); + + for (let i = 0; i < this.dump.length; i++) { + const oredicTimer = new Timer(); + const oredic = this.dump[i]; + const pattern = this.findShortest(oredic, i); + result.set(oredic, pattern); + + const timerResult = oredicTimer.getTime(); + this.telemetry.set(oredic, { + timings: timerResult, + result: { + string: pattern, + length: pattern.length, + shortening: oredic.length - pattern.length, + }, + }); + + if ((i + 1) % PROGRESS_UPDATE_INTERVAL === 0 || i === this.dump.length - 1) { + this.logProgress(i + 1, this.dump.length); + } + } + + const time = stopTimer("shortening").getTime(); + getLogger().simpleLog("success", `Found ${result.size} patterns in ${time.formatted}`); + + return result; + } + + private findShortest(oredic: string, oredicIndex: number): string { + const maxUsefulLength = oredic.length - 2; + + for (let length = 1; length <= maxUsefulLength; length++) { + const pattern = this.tryLength(oredic, oredicIndex, length); + if (pattern) return pattern; + } + + return oredic; + } + + private tryLength(text: string, oredicIndex: number, length: number): string | null { + return this.tryPositions(text, oredicIndex, length, 0, []); + } + + private tryPositions( + text: string, + oredicIndex: number, + remaining: number, + start: number, + positions: number[], + ): string | null { + if (remaining === 0) { + if (this.hasUselessGaps(positions)) { + return null; + } + + const pattern = this.buildPattern(text, positions); + return this.matcher.isUniqueMatch(pattern, oredicIndex) ? pattern : null; + } + + const maxPos = text.length - remaining; + for (let pos = start; pos <= maxPos; pos++) { + positions.push(pos); + const result = this.tryPositions(text, oredicIndex, remaining - 1, pos + 1, positions); + if (result) return result; + positions.pop(); + } + + return null; + } + + private hasUselessGaps(positions: number[]): boolean { + for (let i = 0; i < positions.length - 1; i++) { + const gapSize = positions[i + 1] - positions[i] - 1; + if (gapSize === 1) { + return true; + } + } + return false; + } + + private buildPattern(text: string, positions: number[]): string { + if (positions.length === 0) return ""; + + const parts: string[] = []; + + if (positions[0] > 0) parts.push("*"); + + for (let i = 0; i < positions.length; i++) { + parts.push(text[positions[i]]); + + if (i < positions.length - 1 && positions[i + 1] > positions[i] + 1) { + parts.push("*"); + } + } + + if (positions[positions.length - 1] < text.length - 1) parts.push("*"); + + return parts.join(""); + } + + private loadDump(): string[] { + const dumpContent = DUMPS.get(this.pack); + if (!dumpContent) { + throw new Error(`Failed to load oredic dump for pack: ${this.pack}`); + } + return dumpContent.split("\n"); + } + + private logProgress(current: number, total: number): void { + getLogger().progressBar(current, total, "shortening", 50, "Processing"); + } +} diff --git a/storage/filters/drugs.json b/storage/filters/drugs.json index ab21f99..28578d3 100644 --- a/storage/filters/drugs.json +++ b/storage/filters/drugs.json @@ -1,42 +1,41 @@ { - "name": "Illegal Drugs", - "description": "Filters content related to drug synthesis, manufacturing, and trafficking", - "patterns": [ - { - "pattern": "\\b(synthesize|manufacture|make|produce|cook)\\s+(meth|methamphetamine|cocaine|heroin|fentanyl|lsd|mdma|ecstasy)\\b", - "severity": "high", - "description": "Drug synthesis or manufacturing" - }, - { - "pattern": "\\b(drug\\s+)?(synthesis|manufacturing|production)\\s+(guide|tutorial|method|process)\\b", - "severity": "high", - "description": "Drug production guides" - }, - { - "pattern": "\\bcrack\\s+cocaine\\s+(recipe|method|process)\\b", - "severity": "high", - "description": "Crack cocaine production" - }, - { - "pattern": "\\b(grow|cultivate)\\s+marijuana\\s+(indoor|commercial|large\\s*scale)\\b", - "severity": "medium", - "description": "Large-scale marijuana cultivation" - }, - { - "pattern": "\\b(extract|purify)\\s+(cocaine|heroin|fentanyl|opioid)\\b", - "severity": "high", - "description": "Drug extraction or purification" - }, - { - "pattern": "\\b(meth|methamphetamine)\\s+(lab|recipe|cook|production)\\b", - "severity": "high", - "description": "Methamphetamine production" - }, - { - "pattern": "\\b(drug\\s+)?(trafficking|smuggling)\\s+(method|route|strategy)\\b", - "severity": "high", - "description": "Drug trafficking methods" - } - ] + "name": "Illegal Drugs", + "description": "Filters content related to drug synthesis, manufacturing, and trafficking", + "patterns": [ + { + "pattern": "\\b(synthesize|manufacture|make|produce|cook)\\s+(meth|methamphetamine|cocaine|heroin|fentanyl|lsd|mdma|ecstasy)\\b", + "severity": "high", + "description": "Drug synthesis or manufacturing" + }, + { + "pattern": "\\b(drug\\s+)?(synthesis|manufacturing|production)\\s+(guide|tutorial|method|process)\\b", + "severity": "high", + "description": "Drug production guides" + }, + { + "pattern": "\\bcrack\\s+cocaine\\s+(recipe|method|process)\\b", + "severity": "high", + "description": "Crack cocaine production" + }, + { + "pattern": "\\b(grow|cultivate)\\s+marijuana\\s+(indoor|commercial|large\\s*scale)\\b", + "severity": "medium", + "description": "Large-scale marijuana cultivation" + }, + { + "pattern": "\\b(extract|purify)\\s+(cocaine|heroin|fentanyl|opioid)\\b", + "severity": "high", + "description": "Drug extraction or purification" + }, + { + "pattern": "\\b(meth|methamphetamine)\\s+(lab|recipe|cook|production)\\b", + "severity": "high", + "description": "Methamphetamine production" + }, + { + "pattern": "\\b(drug\\s+)?(trafficking|smuggling)\\s+(method|route|strategy)\\b", + "severity": "high", + "description": "Drug trafficking methods" + } + ] } - diff --git a/storage/filters/exploitation.json b/storage/filters/exploitation.json index 87efc86..67e376e 100644 --- a/storage/filters/exploitation.json +++ b/storage/filters/exploitation.json @@ -1,32 +1,31 @@ { - "name": "Exploitation", - "description": "Filters content related to child exploitation and human trafficking", - "patterns": [ - { - "pattern": "\\b(child|minor)\\s+(exploitation|abuse|pornography|grooming)\\b", - "severity": "critical", - "description": "Child exploitation or abuse" - }, - { - "pattern": "\\b(human|sex)\\s+trafficking\\s+(guide|method|how\\s+to)\\b", - "severity": "critical", - "description": "Human trafficking" - }, - { - "pattern": "\\b(groom|lure|manipulate)\\s+(child|minor|kid)\\b", - "severity": "critical", - "description": "Child grooming" - }, - { - "pattern": "\\b(sexual\\s+)?exploitation\\s+(of\\s+)?(minor|child)\\b", - "severity": "critical", - "description": "Exploitation of minors" - }, - { - "pattern": "\\b(child|minor)\\s+(sexual|pornographic)\\s+(content|material)\\b", - "severity": "critical", - "description": "Child sexual abuse material" - } - ] + "name": "Exploitation", + "description": "Filters content related to child exploitation and human trafficking", + "patterns": [ + { + "pattern": "\\b(child|minor)\\s+(exploitation|abuse|pornography|grooming)\\b", + "severity": "critical", + "description": "Child exploitation or abuse" + }, + { + "pattern": "\\b(human|sex)\\s+trafficking\\s+(guide|method|how\\s+to)\\b", + "severity": "critical", + "description": "Human trafficking" + }, + { + "pattern": "\\b(groom|lure|manipulate)\\s+(child|minor|kid)\\b", + "severity": "critical", + "description": "Child grooming" + }, + { + "pattern": "\\b(sexual\\s+)?exploitation\\s+(of\\s+)?(minor|child)\\b", + "severity": "critical", + "description": "Exploitation of minors" + }, + { + "pattern": "\\b(child|minor)\\s+(sexual|pornographic)\\s+(content|material)\\b", + "severity": "critical", + "description": "Child sexual abuse material" + } + ] } - diff --git a/storage/filters/exploits.json b/storage/filters/exploits.json index e1abdf1..3b3108e 100644 --- a/storage/filters/exploits.json +++ b/storage/filters/exploits.json @@ -1,21 +1,21 @@ { - "name": "Exploits", - "description": "Filters smart people trying to do smart things", - "patterns": [ - { - "pattern": "", - "severity": "high", - "description": "Trying to override the user" - }, - { - "pattern": "\\b(prompt|question):", - "severity": "medium", - "description": "Trying to override the prompt" - }, - { - "pattern": "\\bcontext:", - "severity": "medium", - "description": "Trying to override the context" - } - ] + "name": "Exploits", + "description": "Filters smart people trying to do smart things", + "patterns": [ + { + "pattern": "", + "severity": "high", + "description": "Trying to override the user" + }, + { + "pattern": "\\b(prompt|question):", + "severity": "medium", + "description": "Trying to override the prompt" + }, + { + "pattern": "\\bcontext:", + "severity": "medium", + "description": "Trying to override the context" + } + ] } diff --git a/storage/filters/fraud.json b/storage/filters/fraud.json index 22f4ae0..d7e520e 100644 --- a/storage/filters/fraud.json +++ b/storage/filters/fraud.json @@ -1,47 +1,46 @@ { - "name": "Fraud & Financial Crime", - "description": "Filters content related to fraud, money laundering, and financial crimes", - "patterns": [ - { - "pattern": "\\b(counterfeit|fake)\\s+(money|currency|bills|documents|id)\\b", - "severity": "high", - "description": "Counterfeiting" - }, - { - "pattern": "\\b(credit\\s*card|identity)\\s+(fraud|theft|cloning)\\b", - "severity": "high", - "description": "Credit card fraud or identity theft" - }, - { - "pattern": "\\b(money\\s*laundering|wash\\s+money)\\s+(method|scheme|process)\\b", - "severity": "high", - "description": "Money laundering" - }, - { - "pattern": "\\b(tax\\s+evasion|evade\\s+tax)\\s+(method|scheme|strategy)\\b", - "severity": "medium", - "description": "Tax evasion" - }, - { - "pattern": "\\b(ponzi|pyramid)\\s+scheme\\s+(start|create|run)\\b", - "severity": "high", - "description": "Ponzi or pyramid schemes" - }, - { - "pattern": "\\b(insurance|welfare)\\s+fraud\\s+(commit|scheme)\\b", - "severity": "high", - "description": "Insurance or welfare fraud" - }, - { - "pattern": "\\b(skimming\\s+device|card\\s+skimmer)\\s+(make|build|install)\\b", - "severity": "high", - "description": "Card skimming devices" - }, - { - "pattern": "\\b(fake\\s+id|forged\\s+document)\\s+(create|make|produce)\\b", - "severity": "high", - "description": "Forging documents" - } - ] + "name": "Fraud & Financial Crime", + "description": "Filters content related to fraud, money laundering, and financial crimes", + "patterns": [ + { + "pattern": "\\b(counterfeit|fake)\\s+(money|currency|bills|documents|id)\\b", + "severity": "high", + "description": "Counterfeiting" + }, + { + "pattern": "\\b(credit\\s*card|identity)\\s+(fraud|theft|cloning)\\b", + "severity": "high", + "description": "Credit card fraud or identity theft" + }, + { + "pattern": "\\b(money\\s*laundering|wash\\s+money)\\s+(method|scheme|process)\\b", + "severity": "high", + "description": "Money laundering" + }, + { + "pattern": "\\b(tax\\s+evasion|evade\\s+tax)\\s+(method|scheme|strategy)\\b", + "severity": "medium", + "description": "Tax evasion" + }, + { + "pattern": "\\b(ponzi|pyramid)\\s+scheme\\s+(start|create|run)\\b", + "severity": "high", + "description": "Ponzi or pyramid schemes" + }, + { + "pattern": "\\b(insurance|welfare)\\s+fraud\\s+(commit|scheme)\\b", + "severity": "high", + "description": "Insurance or welfare fraud" + }, + { + "pattern": "\\b(skimming\\s+device|card\\s+skimmer)\\s+(make|build|install)\\b", + "severity": "high", + "description": "Card skimming devices" + }, + { + "pattern": "\\b(fake\\s+id|forged\\s+document)\\s+(create|make|produce)\\b", + "severity": "high", + "description": "Forging documents" + } + ] } - diff --git a/storage/filters/hacking.json b/storage/filters/hacking.json index fab3e59..837e6d2 100644 --- a/storage/filters/hacking.json +++ b/storage/filters/hacking.json @@ -1,52 +1,51 @@ { - "name": "Hacking & Cybercrime", - "description": "Filters content related to malware, hacking, and cyber attacks", - "patterns": [ - { - "pattern": "\\b(create|make|build|develop)\\s+(malware|ransomware|virus|trojan|keylogger|botnet)\\b", - "severity": "high", - "description": "Creating malware" - }, - { - "pattern": "\\b(ddos|dos)\\s+(attack|tool|script|service)\\b", - "severity": "high", - "description": "DDoS attacks" - }, - { - "pattern": "\\b(steal|crack|bypass)\\s+(password|credential|login)\\b", - "severity": "high", - "description": "Credential theft" - }, - { - "pattern": "\\b(exploit|vulnerability)\\s+(kit|framework|tutorial)\\b", - "severity": "high", - "description": "Exploit kits or tutorials" - }, - { - "pattern": "\\bsql\\s*injection\\s+(tutorial|guide|attack)\\b", - "severity": "high", - "description": "SQL injection attacks" - }, - { - "pattern": "\\b(bypass|defeat|circumvent)\\s+(encryption|security|authentication)\\b", - "severity": "high", - "description": "Bypassing security" - }, - { - "pattern": "\\b(credit\\s*card|bank\\s*account)\\s+(hack|exploit|fraud)\\b", - "severity": "high", - "description": "Financial hacking" - }, - { - "pattern": "\\b(zero\\s*day|0day)\\s+(exploit|vulnerability)\\s+(sell|buy|use)\\b", - "severity": "high", - "description": "Zero-day exploits" - }, - { - "pattern": "\\b(phishing\\s+)?(kit|campaign|attack)\\s+(setup|create|launch)\\b", - "severity": "high", - "description": "Phishing campaigns" - } - ] + "name": "Hacking & Cybercrime", + "description": "Filters content related to malware, hacking, and cyber attacks", + "patterns": [ + { + "pattern": "\\b(create|make|build|develop)\\s+(malware|ransomware|virus|trojan|keylogger|botnet)\\b", + "severity": "high", + "description": "Creating malware" + }, + { + "pattern": "\\b(ddos|dos)\\s+(attack|tool|script|service)\\b", + "severity": "high", + "description": "DDoS attacks" + }, + { + "pattern": "\\b(steal|crack|bypass)\\s+(password|credential|login)\\b", + "severity": "high", + "description": "Credential theft" + }, + { + "pattern": "\\b(exploit|vulnerability)\\s+(kit|framework|tutorial)\\b", + "severity": "high", + "description": "Exploit kits or tutorials" + }, + { + "pattern": "\\bsql\\s*injection\\s+(tutorial|guide|attack)\\b", + "severity": "high", + "description": "SQL injection attacks" + }, + { + "pattern": "\\b(bypass|defeat|circumvent)\\s+(encryption|security|authentication)\\b", + "severity": "high", + "description": "Bypassing security" + }, + { + "pattern": "\\b(credit\\s*card|bank\\s*account)\\s+(hack|exploit|fraud)\\b", + "severity": "high", + "description": "Financial hacking" + }, + { + "pattern": "\\b(zero\\s*day|0day)\\s+(exploit|vulnerability)\\s+(sell|buy|use)\\b", + "severity": "high", + "description": "Zero-day exploits" + }, + { + "pattern": "\\b(phishing\\s+)?(kit|campaign|attack)\\s+(setup|create|launch)\\b", + "severity": "high", + "description": "Phishing campaigns" + } + ] } - diff --git a/storage/filters/poisons.json b/storage/filters/poisons.json index 0dbc11c..a119e74 100644 --- a/storage/filters/poisons.json +++ b/storage/filters/poisons.json @@ -1,32 +1,31 @@ { - "name": "Dangerous Substances & Poisons", - "description": "Filters content related to poisons, toxins, and chemical/biological weapons", - "patterns": [ - { - "pattern": "\\b(poison|toxin)\\s+(recipe|method|create|make)\\b", - "severity": "high", - "description": "Creating poisons or toxins" - }, - { - "pattern": "\\b(ricin|cyanide|arsenic)\\s+(extract|produce|obtain)\\b", - "severity": "critical", - "description": "Specific deadly poisons" - }, - { - "pattern": "\\b(deadly|lethal)\\s+(poison|substance|chemical)\\b", - "severity": "high", - "description": "Lethal substances" - }, - { - "pattern": "\\b(nerve\\s+agent|chemical\\s+weapon)\\s+(create|make|synthesize)\\b", - "severity": "critical", - "description": "Chemical weapons" - }, - { - "pattern": "\\b(biological\\s+weapon|bioweapon)\\s+(create|develop)\\b", - "severity": "critical", - "description": "Biological weapons" - } - ] + "name": "Dangerous Substances & Poisons", + "description": "Filters content related to poisons, toxins, and chemical/biological weapons", + "patterns": [ + { + "pattern": "\\b(poison|toxin)\\s+(recipe|method|create|make)\\b", + "severity": "high", + "description": "Creating poisons or toxins" + }, + { + "pattern": "\\b(ricin|cyanide|arsenic)\\s+(extract|produce|obtain)\\b", + "severity": "critical", + "description": "Specific deadly poisons" + }, + { + "pattern": "\\b(deadly|lethal)\\s+(poison|substance|chemical)\\b", + "severity": "high", + "description": "Lethal substances" + }, + { + "pattern": "\\b(nerve\\s+agent|chemical\\s+weapon)\\s+(create|make|synthesize)\\b", + "severity": "critical", + "description": "Chemical weapons" + }, + { + "pattern": "\\b(biological\\s+weapon|bioweapon)\\s+(create|develop)\\b", + "severity": "critical", + "description": "Biological weapons" + } + ] } - diff --git a/storage/filters/privacy.json b/storage/filters/privacy.json index ef39046..29b836f 100644 --- a/storage/filters/privacy.json +++ b/storage/filters/privacy.json @@ -1,32 +1,31 @@ { - "name": "Privacy Violation & Harassment", - "description": "Filters content related to doxxing, stalking, and harassment", - "patterns": [ - { - "pattern": "\\b(dox|doxx|swat)\\s+(someone|person|user)\\b", - "severity": "high", - "description": "Doxxing or swatting" - }, - { - "pattern": "\\b(find|get|obtain)\\s+(personal\\s+information|home\\s+address|phone\\s+number)\\s+(of|for)\\b", - "severity": "high", - "description": "Obtaining personal information" - }, - { - "pattern": "\\b(stalk|track|locate)\\s+(someone|person)\\s+(online|offline)\\b", - "severity": "high", - "description": "Stalking or tracking" - }, - { - "pattern": "\\bswatting\\s+(guide|method|how\\s+to)\\b", - "severity": "high", - "description": "Swatting guides" - }, - { - "pattern": "\\b(revenge|non-consensual)\\s+(porn|intimate\\s+images)\\b", - "severity": "high", - "description": "Revenge porn" - } - ] + "name": "Privacy Violation & Harassment", + "description": "Filters content related to doxxing, stalking, and harassment", + "patterns": [ + { + "pattern": "\\b(dox|doxx|swat)\\s+(someone|person|user)\\b", + "severity": "high", + "description": "Doxxing or swatting" + }, + { + "pattern": "\\b(find|get|obtain)\\s+(personal\\s+information|home\\s+address|phone\\s+number)\\s+(of|for)\\b", + "severity": "high", + "description": "Obtaining personal information" + }, + { + "pattern": "\\b(stalk|track|locate)\\s+(someone|person)\\s+(online|offline)\\b", + "severity": "high", + "description": "Stalking or tracking" + }, + { + "pattern": "\\bswatting\\s+(guide|method|how\\s+to)\\b", + "severity": "high", + "description": "Swatting guides" + }, + { + "pattern": "\\b(revenge|non-consensual)\\s+(porn|intimate\\s+images)\\b", + "severity": "high", + "description": "Revenge porn" + } + ] } - diff --git a/storage/filters/self-harm.json b/storage/filters/self-harm.json index b7a645e..e8b3230 100644 --- a/storage/filters/self-harm.json +++ b/storage/filters/self-harm.json @@ -1,37 +1,36 @@ { - "name": "Self-Harm & Suicide", - "description": "Filters content related to self-harm and suicide methods", - "patterns": [ - { - "pattern": "\\b(painless|easiest|quickest)\\s+(way\\s+to\\s+)?(commit\\s+suicide|kill\\s+myself|end\\s+my\\s+life)\\b", - "severity": "critical", - "description": "Suicide methods" - }, - { - "pattern": "\\b(suicide|overdose)\\s+(method|technique|guide)\\b", - "severity": "critical", - "description": "Suicide guides" - }, - { - "pattern": "\\b(cut|harm)\\s+myself\\s+(guide|method|how)\\b", - "severity": "high", - "description": "Self-harm methods" - }, - { - "pattern": "\\b(hanging|poisoning|overdose)\\s+(method|instructions)\\b", - "severity": "critical", - "description": "Specific suicide methods" - }, - { - "pattern": "\\b(best|effective)\\s+(suicide|self\\s*harm)\\s+method\\b", - "severity": "critical", - "description": "Seeking effective methods" - }, - { - "pattern": "\\bcommit\\s+suicide\\s+(with|using)\\b", - "severity": "critical", - "description": "Suicide planning" - } - ] + "name": "Self-Harm & Suicide", + "description": "Filters content related to self-harm and suicide methods", + "patterns": [ + { + "pattern": "\\b(painless|easiest|quickest)\\s+(way\\s+to\\s+)?(commit\\s+suicide|kill\\s+myself|end\\s+my\\s+life)\\b", + "severity": "critical", + "description": "Suicide methods" + }, + { + "pattern": "\\b(suicide|overdose)\\s+(method|technique|guide)\\b", + "severity": "critical", + "description": "Suicide guides" + }, + { + "pattern": "\\b(cut|harm)\\s+myself\\s+(guide|method|how)\\b", + "severity": "high", + "description": "Self-harm methods" + }, + { + "pattern": "\\b(hanging|poisoning|overdose)\\s+(method|instructions)\\b", + "severity": "critical", + "description": "Specific suicide methods" + }, + { + "pattern": "\\b(best|effective)\\s+(suicide|self\\s*harm)\\s+method\\b", + "severity": "critical", + "description": "Seeking effective methods" + }, + { + "pattern": "\\bcommit\\s+suicide\\s+(with|using)\\b", + "severity": "critical", + "description": "Suicide planning" + } + ] } - diff --git a/storage/filters/violence.json b/storage/filters/violence.json index b546769..2f488da 100644 --- a/storage/filters/violence.json +++ b/storage/filters/violence.json @@ -1,32 +1,31 @@ { - "name": "Hateful & Violent Content", - "description": "Filters content related to terrorism, mass violence, and hate groups", - "patterns": [ - { - "pattern": "\\b(plan|commit|carry\\s*out)\\s+(mass\\s+shooting|school\\s+shooting|terrorist\\s+attack)\\b", - "severity": "critical", - "description": "Planning mass violence" - }, - { - "pattern": "\\b(genocide|ethnic\\s+cleansing)\\s+(plan|method|justify)\\b", - "severity": "critical", - "description": "Genocide or ethnic cleansing" - }, - { - "pattern": "\\b(neo\\s*nazi|white\\s+supremac|kkk)\\s+(recruit|propaganda|manifesto)\\b", - "severity": "high", - "description": "Hate group propaganda" - }, - { - "pattern": "\\b(terror|terrorist)\\s+(attack|plot|plan)\\s+(execute|carry\\s*out)\\b", - "severity": "critical", - "description": "Terrorist planning" - }, - { - "pattern": "\\b(mass|spree)\\s+(shooting|killing)\\s+(plan|method)\\b", - "severity": "critical", - "description": "Mass killing planning" - } - ] + "name": "Hateful & Violent Content", + "description": "Filters content related to terrorism, mass violence, and hate groups", + "patterns": [ + { + "pattern": "\\b(plan|commit|carry\\s*out)\\s+(mass\\s+shooting|school\\s+shooting|terrorist\\s+attack)\\b", + "severity": "critical", + "description": "Planning mass violence" + }, + { + "pattern": "\\b(genocide|ethnic\\s+cleansing)\\s+(plan|method|justify)\\b", + "severity": "critical", + "description": "Genocide or ethnic cleansing" + }, + { + "pattern": "\\b(neo\\s*nazi|white\\s+supremac|kkk)\\s+(recruit|propaganda|manifesto)\\b", + "severity": "high", + "description": "Hate group propaganda" + }, + { + "pattern": "\\b(terror|terrorist)\\s+(attack|plot|plan)\\s+(execute|carry\\s*out)\\b", + "severity": "critical", + "description": "Terrorist planning" + }, + { + "pattern": "\\b(mass|spree)\\s+(shooting|killing)\\s+(plan|method)\\b", + "severity": "critical", + "description": "Mass killing planning" + } + ] } - diff --git a/storage/filters/weapons.json b/storage/filters/weapons.json index c8b5697..cf1c37e 100644 --- a/storage/filters/weapons.json +++ b/storage/filters/weapons.json @@ -1,42 +1,41 @@ { - "name": "Weapons & Explosives", - "description": "Filters content related to creating weapons, explosives, and illegal firearms", - "patterns": [ - { - "pattern": "\\b(make|build|create|craft|assemble)\\s+(a\\s+)?(bomb|explosive|grenade|molotov|pipe\\s*bomb|ied)\\b", - "severity": "high", - "description": "Creating explosives or bombs" - }, - { - "pattern": "\\b(gun|firearm|rifle|pistol|weapon)\\s+(blueprint|schematic|plan|design)\\b", - "severity": "high", - "description": "Weapon blueprints or designs" - }, - { - "pattern": "\\b(illegal|unregistered|unlicensed)\\s+(weapon|firearm|gun)\\b", - "severity": "medium", - "description": "Illegal weapons" - }, - { - "pattern": "\\b3d\\s*print(ed|ing)?\\s+(gun|firearm|weapon)\\b", - "severity": "high", - "description": "3D printed weapons" - }, - { - "pattern": "\\bammonium\\s*nitrate\\s+(explosive|bomb)\\b", - "severity": "high", - "description": "Explosive materials" - }, - { - "pattern": "\\b(assemble|build)\\s+(ar-?15|ak-?47|semi\\s*auto)\\b", - "severity": "medium", - "description": "Assembling specific firearms" - }, - { - "pattern": "\\b(silencer|suppressor)\\s+(diy|homemade|build|make)\\b", - "severity": "high", - "description": "DIY weapon suppressors" - } - ] + "name": "Weapons & Explosives", + "description": "Filters content related to creating weapons, explosives, and illegal firearms", + "patterns": [ + { + "pattern": "\\b(make|build|create|craft|assemble)\\s+(a\\s+)?(bomb|explosive|grenade|molotov|pipe\\s*bomb|ied)\\b", + "severity": "high", + "description": "Creating explosives or bombs" + }, + { + "pattern": "\\b(gun|firearm|rifle|pistol|weapon)\\s+(blueprint|schematic|plan|design)\\b", + "severity": "high", + "description": "Weapon blueprints or designs" + }, + { + "pattern": "\\b(illegal|unregistered|unlicensed)\\s+(weapon|firearm|gun)\\b", + "severity": "medium", + "description": "Illegal weapons" + }, + { + "pattern": "\\b3d\\s*print(ed|ing)?\\s+(gun|firearm|weapon)\\b", + "severity": "high", + "description": "3D printed weapons" + }, + { + "pattern": "\\bammonium\\s*nitrate\\s+(explosive|bomb)\\b", + "severity": "high", + "description": "Explosive materials" + }, + { + "pattern": "\\b(assemble|build)\\s+(ar-?15|ak-?47|semi\\s*auto)\\b", + "severity": "medium", + "description": "Assembling specific firearms" + }, + { + "pattern": "\\b(silencer|suppressor)\\s+(diy|homemade|build|make)\\b", + "severity": "high", + "description": "DIY weapon suppressors" + } + ] } - diff --git a/storage/oredic/dumps/.gitkeep b/storage/oredic/dumps/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..48f99ce --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*", "tools/**/*", "*.config.*"], + "exclude": ["node_modules", "dist"] +} diff --git a/tsconfig.json b/tsconfig.json index 5726f20..3445638 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,12 @@ { - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "typeRoots": ["./node_modules/@types", "./src/types/*"] - } + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "typeRoots": ["./node_modules/@types", "./src/types/*"] + }, + "exclude": ["*.config.*", "node_modules", "dist", "tools"] }