diff --git a/.lintstagedrc b/.lintstagedrc deleted file mode 100644 index a8a47eb..0000000 --- a/.lintstagedrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "*.{j,t}s": ["eslint", "prettier --write"], - "src/schemas/{*,**/*}.ts": ["eslint", "prettier --write", "tsc -b -v", "node scripts/schema.js", "node scripts/openapi.js" ] -} diff --git a/.lintstagedrc.js b/.lintstagedrc.js new file mode 100644 index 0000000..76ea0b0 --- /dev/null +++ b/.lintstagedrc.js @@ -0,0 +1,4 @@ +module.exports = { + "*.{j,t}s": ["eslint", "prettier --write"], + "src/schemas/{*,**/*}.ts": [() => "tsc -b -v", () => "node scripts/schema.js", () => "node scripts/openapi.js"], +}; diff --git a/.prettierrc.json b/.prettierrc.json index 6172f0c..ebfbac3 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,13 +1,13 @@ { - "trailingComma": "all", - "tabWidth": 4, - "semi": true, - "arrowParens": "always", - "bracketSameLine": false, - "bracketSpacing": true, - "quoteProps": "as-needed", - "useTabs": false, - "singleQuote": false, - "endOfLine": "auto", - "printWidth": 180 + "trailingComma": "all", + "tabWidth": 4, + "semi": true, + "arrowParens": "always", + "bracketSameLine": false, + "bracketSpacing": true, + "quoteProps": "as-needed", + "useTabs": false, + "singleQuote": false, + "endOfLine": "auto", + "printWidth": 180 } diff --git a/eslint.config.mjs b/eslint.config.mjs index d0c0658..17e64dc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,35 +9,35 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, }); export default [ - { - ignores: ["**/node_modules", "**/dist", "**/README.md", "**/COPYING", "**/scripts/", "**/assets", "**/extra/"], - }, - ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), - { - plugins: { - "@typescript-eslint": typescriptEslint, - }, + { + ignores: ["**/node_modules", "**/dist", "**/README.md", "**/COPYING", "**/scripts/", "**/assets", "**/extra/"], + }, + ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), + { + plugins: { + "@typescript-eslint": typescriptEslint, + }, - languageOptions: { - globals: { - ...globals.node, - }, + languageOptions: { + globals: { + ...globals.node, + }, - parser: tsParser, - }, + parser: tsParser, + }, - rules: { - "no-mixed-spaces-and-tabs": "off", - "@typescript-eslint/no-inferrable-types": "off", // Required by typeorm - "@typescript-eslint/no-var-requires": "off", // Sometimes requred by typeorm to resolve circular deps - "@typescript-eslint/no-require-imports": "off", - "@typescript-eslint/no-unused-vars": "off", - }, - }, + rules: { + "no-mixed-spaces-and-tabs": "off", + "@typescript-eslint/no-inferrable-types": "off", // Required by typeorm + "@typescript-eslint/no-var-requires": "off", // Sometimes requred by typeorm to resolve circular deps + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/no-unused-vars": "off", + }, + }, ]; diff --git a/package-lock.json b/package-lock.json index ed7b29c..2c0a698 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10195 +1,10195 @@ { - "name": "spacebar-server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "spacebar-server", - "version": "1.0.0", - "hasInstallScript": true, - "license": "AGPL-3.0-only", - "dependencies": { - "@aws-sdk/client-s3": "^3.953.0", - "@toondepauw/node-zstd": "^1.2.0", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "amqplib": "^0.10.9", - "bcrypt": "^6.0.0", - "body-parser": "^2.2.1", - "chalk": "^5.6.2", - "cheerio": "^1.1.2", - "cookie-parser": "^1.4.7", - "discord-protos": "^1.2.90", - "dotenv": "^17.2.3", - "email-providers": "^2.19.0", - "exif-be-gone": "^1.5.1", - "express": "^5.2.1", - "fast-zlib": "^2.0.1", - "fido2-lib": "^3.5.5", - "file-type": "^21.1.1", - "form-data": "^4.0.5", - "i18next": "^25.7.3", - "i18next-fs-backend": "^2.6.1", - "i18next-http-middleware": "^3.9.0", - "image-size": "^2.0.2", - "json-bigint": "^1.0.0", - "jsonwebtoken": "^9.0.3", - "module-alias": "^2.2.3", - "morgan": "^1.10.1", - "ms": "^2.1.3", - "multer": "^2.0.2", - "murmurhash-js": "^1.0.0", - "node-2fa": "^2.0.3", - "pg": "^8.16.3", - "pg-query-stream": "^4.10.3", - "picocolors": "^1.1.1", - "probe-image-size": "^7.2.3", - "reflect-metadata": "^0.2.2", - "sqlite3": "^5.1.7", - "tslib": "^2.8.1", - "typeorm": "^0.3.28", - "wretch": "^2.11.1", - "ws": "^8.18.3" - }, - "devDependencies": { - "@eslint/eslintrc": "^3.3.3", - "@eslint/js": "^9.14.0", - "@spacebarchat/spacebar-webrtc-types": "^1.0.1", - "@types/amqplib": "^0.10.8", - "@types/bcrypt": "^6.0.0", - "@types/body-parser": "^1.19.6", - "@types/cookie-parser": "^1.4.10", - "@types/express": "^5.0.6", - "@types/i18next-node-fs-backend": "^2.1.5", - "@types/json-bigint": "^1.0.4", - "@types/jsonwebtoken": "^9.0.10", - "@types/module-alias": "^2.0.4", - "@types/morgan": "^1.9.10", - "@types/multer": "^2.0.0", - "@types/murmurhash-js": "^1.0.6", - "@types/node": "^24.10.4", - "@types/nodemailer": "^7.0.4", - "@types/probe-image-size": "^7.2.5", - "@types/sharp": "^0.31.1", - "@types/ws": "^8.18.1", - "@typescript-eslint/eslint-plugin": "^8.50.0", - "@typescript-eslint/parser": "^8.50.0", - "eslint": "^9.39.2", - "globals": "^16.5.0", - "husky": "^9.1.7", - "patch-package": "^8.0.1", - "prettier": "^3.7.4", - "pretty-quick": "^4.2.2", - "ts-node": "^10.9.2", - "typescript": "^5.9.3", - "typescript-json-schema": "^0.65.1" - }, - "optionalDependencies": { - "@sendgrid/mail": "^8.1.6", - "@yukikaze-bot/erlpack": "^1.0.1", - "jimp": "^1.6.0", - "mailgun.js": "^12.4.0", - "node-mailjet": "^6.0.11", - "nodemailer": "^7.0.11" - } - }, - "node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/crc32c": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", - "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha1-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", - "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.953.0.tgz", - "integrity": "sha512-Lxxdhq5nt6ONulu1UHbIS0tVIar7itXv1m4TJfkVzuSm/yQzxIwnFkLtgW/0P5KIE+FS1yUoE2lS+dJBS1PLFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha1-browser": "5.2.0", - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/credential-provider-node": "3.953.0", - "@aws-sdk/middleware-bucket-endpoint": "3.953.0", - "@aws-sdk/middleware-expect-continue": "3.953.0", - "@aws-sdk/middleware-flexible-checksums": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-location-constraint": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-sdk-s3": "3.953.0", - "@aws-sdk/middleware-ssec": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/signature-v4-multi-region": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/eventstream-serde-browser": "^4.2.6", - "@smithy/eventstream-serde-config-resolver": "^4.3.6", - "@smithy/eventstream-serde-node": "^4.2.6", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-blob-browser": "^4.2.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/hash-stream-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/md5-js": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", - "@smithy/util-stream": "^4.5.7", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sesv2": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.953.0.tgz", - "integrity": "sha512-8GkTGBfFB6UbaGpXt2ZgYql+prCYLBQeSQYGb+vBZ8TnU6L6badhC8g9QkCYO2ul6rm02+MBrFgcNx40NSAsoA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/credential-provider-node": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/signature-v4-multi-region": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.953.0.tgz", - "integrity": "sha512-WU8XtORGnhy1JjTLflcGcNGU329pmwl/2claTVGp7vrgUKyreQV9Ke3BkMuUPYLOGO/Znstzc1VbUT9ORH3+WQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.953.0.tgz", - "integrity": "sha512-hnz4ukn+XupuotZcFAyCIX41QqEWSHc8zf4gN4l5qVP2M8YCyZkLLZ5t5LaWLJkUFbOUU2w4SuGpUp+5ddtSkw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@aws-sdk/xml-builder": "3.953.0", - "@smithy/core": "^3.19.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/signature-v4": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.953.0.tgz", - "integrity": "sha512-ahOxDxvaKUf99LU9iRmRtPW2HLmsR+ix2WyzRGl1PpLltiRLMbKJHj5Tu2xyOsgLxs8tefK9D1j0SUgPk8GJ6g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.953.0.tgz", - "integrity": "sha512-kYQEcJpJLiT+1ZdsQDMrIs2o3YydxdAIDdLUxbIwks1+WQz2sCr9b94ld19aMZ0rejosASO0J3dfY0lt3Tfl+Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/util-stream": "^4.5.7", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.953.0.tgz", - "integrity": "sha512-5cy6b3VntBvhRCanwohRtR0wbKKUd6JfIsnuXImB4JcZa2iMW5tDW0LhNangYZ9wrrM7sJol6cKHtUW9LNemFQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/credential-provider-env": "3.953.0", - "@aws-sdk/credential-provider-http": "3.953.0", - "@aws-sdk/credential-provider-login": "3.953.0", - "@aws-sdk/credential-provider-process": "3.953.0", - "@aws-sdk/credential-provider-sso": "3.953.0", - "@aws-sdk/credential-provider-web-identity": "3.953.0", - "@aws-sdk/nested-clients": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/credential-provider-imds": "^4.2.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.953.0.tgz", - "integrity": "sha512-Drf4v7uBKnU/nY/qgQD9ZA2zD5gjQEatxQajQHUN54erDSQ2rB5ApNuTnc2cyNwaxURnQvO1J72mwO6P5lIpsg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/nested-clients": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.953.0.tgz", - "integrity": "sha512-/3gUap/QwAV/U3RPStcSjdiksDboTdcz82qajfhURhtAe9iEdoKJy7vTYqg75eX7IjpgBwLGajt0URasUofrPg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.953.0", - "@aws-sdk/credential-provider-http": "3.953.0", - "@aws-sdk/credential-provider-ini": "3.953.0", - "@aws-sdk/credential-provider-process": "3.953.0", - "@aws-sdk/credential-provider-sso": "3.953.0", - "@aws-sdk/credential-provider-web-identity": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/credential-provider-imds": "^4.2.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.953.0.tgz", - "integrity": "sha512-YeqzqxzBzXnqdzn4pBuoXeCmWrXOLIu5owAowUc4dOvHmtlpXxB3beJdQ9g/Ky6fG6iaAPKO1vrCEoHNjM85Dw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.953.0.tgz", - "integrity": "sha512-Kp/49vAJweixMo3q85eVpq1JO9KgKc6A+f8/8WDkN5CkMsfKQcGJZbO5JtuJfzar4pPmRKbgOeOP1qQsOmfyfw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.953.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/token-providers": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.953.0.tgz", - "integrity": "sha512-V8SpGVtNjQZboHjb/GLM4L3/6O/AZJFtwJ8fiBaemKMTntl8haioZlQHDklWixR9s9zthOdFsCDs1+92ndUBRw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/nested-clients": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.953.0.tgz", - "integrity": "sha512-YHVRIOowtGIl/L2WuS83FgRlm31tU0aL1yryWaFtF+AFjA5BIeiFkxIZqaRGxJpJvFEBdohsyq6Ipv5mgWfezg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-arn-parser": "3.953.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-config-provider": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.953.0.tgz", - "integrity": "sha512-BQTVXrypQ0rbb7au/Hk4IS5GaJZlwk6O44Rjk6Kxb0IvGQhSurNTuesFiJx1sLbf+w+T31saPtODcfQQERqhCQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.953.0.tgz", - "integrity": "sha512-wpCoYg2ok0oSOupFiSPPiXvAhnXce2FkqMt/Bao+kyG3yx26NVG/4/PBftTDVCsJwuDvX/Fw8EuwbAXVmmYLCg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@aws-crypto/crc32c": "5.2.0", - "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-stream": "^4.5.7", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.953.0.tgz", - "integrity": "sha512-jTGhfkONav+r4E6HLOrl5SzBqDmPByUYCkyB/c/3TVb8jX3wAZx8/q9bphKpCh+G5ARi3IdbSisgkZrJYqQ19Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.953.0.tgz", - "integrity": "sha512-h0urrbteIQEybyIISaJfQLZ/+/lJPRzPWAQT4epvzfgv/4MKZI7K83dK7SfTwAooVKFBHiCMok2Cf0iHDt07Kw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.953.0.tgz", - "integrity": "sha512-PlWdVYgcuptkIC0ZKqVUhWNtSHXJSx7U9V8J7dJjRmsXC40X7zpEycvrkzDMJjeTDGcCceYbyYAg/4X1lkcIMw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.953.0.tgz", - "integrity": "sha512-cmIJx0gWeesUKK4YwgE+VQL3mpACr3/J24fbwnc1Z5tntC86b+HQFzU5vsBDw6lLwyD46dBgWdsXFh1jL+ZaFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.953.0.tgz", - "integrity": "sha512-qubXS4GeDdoF7nqq2KEqW6jG7OjifwNfb/m6o5b6o7ZF2OSBr1MqyLykDWqnLjLI4NmM4qRPl5oTDGV0kBqoDA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-arn-parser": "3.953.0", - "@smithy/core": "^3.19.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/signature-v4": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-stream": "^4.5.7", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.953.0.tgz", - "integrity": "sha512-OrhG1kcQ9zZh3NS3RovR028N0+UndQ957zF1k5HPLeFLwFwQN1uPOufzzPzAyXIIKtR69ARFsQI4mstZS4DMvw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.953.0.tgz", - "integrity": "sha512-xZSU54cEHlIvRTUewyTAajb4WJPdbBd1mIaDYOzac07+vsfMuCREQ5CheQ3inoBfqske7QOX+mIjLpVV4azAnw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@smithy/core": "^3.19.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.953.0.tgz", - "integrity": "sha512-YEBI0b/4ezNkw5W4+RBRUoueFzqEPPrnkh/3LBqeJ7RWZTymBhxlpoLo6Gfxw9LxtDgv2vZ5K5rgC4/6Ly8ADg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.953.0", - "@aws-sdk/middleware-host-header": "3.953.0", - "@aws-sdk/middleware-logger": "3.953.0", - "@aws-sdk/middleware-recursion-detection": "3.953.0", - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/region-config-resolver": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@aws-sdk/util-endpoints": "3.953.0", - "@aws-sdk/util-user-agent-browser": "3.953.0", - "@aws-sdk/util-user-agent-node": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/core": "^3.19.0", - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/hash-node": "^4.2.6", - "@smithy/invalid-dependency": "^4.2.6", - "@smithy/middleware-content-length": "^4.2.6", - "@smithy/middleware-endpoint": "^4.3.15", - "@smithy/middleware-retry": "^4.4.15", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/smithy-client": "^4.10.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.14", - "@smithy/util-defaults-mode-node": "^4.2.17", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.953.0.tgz", - "integrity": "sha512-5MJgnsc+HLO+le0EK1cy92yrC7kyhGZSpaq8PcQvKs9qtXCXT5Tb6tMdkr5Y07JxYsYOV1omWBynvL6PWh08tQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/config-resolver": "^4.4.4", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.953.0.tgz", - "integrity": "sha512-9xDZTrUveWHLJDiXWW7UdrjtdoqlWijQlxIWRMAlxruqdXJDSRkn6taSAs7fGRwHJfxjiDsn84Fcqwx5Lw55zg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/signature-v4": "^5.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.953.0.tgz", - "integrity": "sha512-4FWWR3lDDuIftmCEWpGejfkJu2J1VG2MUktChQshADXABfVfM0fsT7OYlO7Sy106nOe9upE4uQTWXg9YT/6ssw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.953.0", - "@aws-sdk/nested-clients": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.953.0.tgz", - "integrity": "sha512-M9Iwg9kTyqTErI0vOTVVpcnTHWzS3VplQppy8MuL02EE+mJ0BIwpWfsaAPQW+/XnVpdNpWZTsHcNE29f1+hR8g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.953.0.tgz", - "integrity": "sha512-9hqdKkn4OvYzzaLryq2xnwcrPc8ziY34i9szUdgBfSqEC6pBxbY9/lLXmrgzfwMSL2Z7/v2go4Od0p5eukKLMQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.953.0.tgz", - "integrity": "sha512-rjaS6jrFksopXvNg6YeN+D1lYwhcByORNlFuYesFvaQNtPOufbE5tJL4GJ3TMXyaY0uFR28N5BHHITPyWWfH/g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-endpoints": "^3.2.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.953.0.tgz", - "integrity": "sha512-mPxK+I1LcrgC/RSa3G5AMAn8eN2Ay0VOgw8lSRmV1jCtO+iYvNeCqOdxoJUjOW6I5BA4niIRWqVORuRP07776Q==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.953.0.tgz", - "integrity": "sha512-UF5NeqYesWuFao+u7LJvpV1SJCaLml5BtFZKUdTnNNMeN6jvV+dW/eQoFGpXF94RCqguX0XESmRuRRPQp+/rzQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.953.0", - "@smithy/types": "^4.10.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.953.0.tgz", - "integrity": "sha512-HjJ0Nq/57kg0QCjt8mwBAK6C34njKezy9ItUNcrWITzrBZv7ikQtyqM1pindAIzgLaBb7ly/0kbJqMY+NPbcJA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.953.0", - "@aws-sdk/types": "3.953.0", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.953.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.953.0.tgz", - "integrity": "sha512-Zmrj21jQ2OeOJGr9spPiN00aQvXa/WUqRXcTVENhrMt+OFoSOfDFpYhUj9NQ09QmQ8KMWFoWuWW6iKurNqLvAA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", - "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@borewit/text-codec": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", - "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", - "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@cbor-extract/cbor-extract-darwin-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", - "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-arm": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", - "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-arm64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", - "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-linux-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", - "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@cbor-extract/cbor-extract-win32-x64": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", - "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "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==", - "dev": true, - "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/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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "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/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/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==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "license": "MIT", - "optional": true - }, - "node_modules/@hexagon/base64": { - "version": "1.1.28", - "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", - "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", - "license": "MIT" - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@jimp/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", - "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/file-ops": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "await-to-js": "^3.0.0", - "exif-parser": "^0.1.12", - "file-type": "^16.0.0", - "mime": "3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/core/node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", - "license": "MIT", - "optional": true, - "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/@jimp/core/node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@jimp/core/node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@jimp/diff": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", - "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "pixelmatch": "^5.3.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/file-ops": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", - "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-bmp": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", - "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "bmp-ts": "^1.0.9" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-gif": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", - "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "gifwrap": "^0.10.1", - "omggif": "^1.0.10" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-jpeg": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", - "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "jpeg-js": "^0.4.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-png": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", - "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "pngjs": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/js-tiff": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", - "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "utif2": "^4.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-blit": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", - "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-blur": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", - "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/utils": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-circle": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", - "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-color": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", - "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "tinycolor2": "^1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-contain": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", - "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-cover": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", - "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-crop": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", - "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-displace": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", - "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-dither": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", - "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/types": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-fisheye": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", - "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-flip": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", - "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-hash": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", - "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "any-base": "^1.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-mask": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", - "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-print": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", - "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/types": "1.6.0", - "parse-bmfont-ascii": "^1.0.6", - "parse-bmfont-binary": "^1.0.6", - "parse-bmfont-xml": "^1.1.6", - "simple-xml-to-json": "^1.2.2", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-quantize": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", - "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", - "license": "MIT", - "optional": true, - "dependencies": { - "image-q": "^4.0.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-resize": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", - "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-rotate": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", - "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/plugin-threshold": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", - "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/types": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", - "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", - "license": "MIT", - "optional": true, - "dependencies": { - "zod": "^3.23.8" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@jimp/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/types": "1.6.0", - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "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==", - "devOptional": 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==", - "devOptional": 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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", - "optional": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@peculiar/asn1-schema": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", - "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", - "license": "MIT", - "dependencies": { - "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@peculiar/webcrypto": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.6.tgz", - "integrity": "sha512-YBcMfqNSwn3SujUJvAaySy5tlYbYm6tVt9SKoXu8BaTdKGROiJDgPR3TXpZdAKUfklzm3lRapJEAltiMQtBgZg==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2", - "webcrypto-core": "^1.7.9" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@protobuf-ts/runtime": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz", - "integrity": "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ==", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "node_modules/@sendgrid/client": { - "version": "8.1.6", - "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.6.tgz", - "integrity": "sha512-/BHu0hqwXNHr2aLhcXU7RmmlVqrdfrbY9KpaNj00KZHlVOVoRxRVrpOCabIB+91ISXJ6+mLM9vpaVUhK6TwBWA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@sendgrid/helpers": "^8.0.0", - "axios": "^1.12.0" - }, - "engines": { - "node": ">=12.*" - } - }, - "node_modules/@sendgrid/helpers": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", - "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", - "license": "MIT", - "optional": true, - "dependencies": { - "deepmerge": "^4.2.2" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/@sendgrid/mail": { - "version": "8.1.6", - "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.6.tgz", - "integrity": "sha512-/ZqxUvKeEztU9drOoPC/8opEPOk+jLlB2q4+xpx6HVLq6aFu3pMpalkTpAQz8XfRfpLp8O25bh6pGPcHDCYpqg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@sendgrid/client": "^8.1.5", - "@sendgrid/helpers": "^8.0.0" - }, - "engines": { - "node": ">=12.*" - } - }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.6.tgz", - "integrity": "sha512-P7JD4J+wxHMpGxqIg6SHno2tPkZbBUBLbPpR5/T1DEUvw/mEaINBMaPFZNM7lA+ToSCZ36j6nMHa+5kej+fhGg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/chunked-blob-reader": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", - "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/chunked-blob-reader-native": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", - "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.4.tgz", - "integrity": "sha512-s3U5ChS21DwU54kMmZ0UJumoS5cg0+rGVZvN6f5Lp6EbAVi0ZyP+qDSHdewfmXKUgNK1j3z45JyzulkDukrjAA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.6", - "@smithy/util-middleware": "^4.2.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.19.0.tgz", - "integrity": "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^4.2.7", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-stream": "^4.5.7", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.6.tgz", - "integrity": "sha512-xBmawExyTzOjbhzkZwg+vVm/khg28kG+rj2sbGlULjFd1jI70sv/cbpaR0Ev4Yfd6CpDUDRMe64cTqR//wAOyA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-codec": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.6.tgz", - "integrity": "sha512-OZfsI+YRG26XZik/jKMMg37acnBSbUiK/8nETW3uM3mLj+0tMmFXdHQw1e5WEd/IHN8BGOh3te91SNDe2o4RHg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.10.0", - "@smithy/util-hex-encoding": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.6.tgz", - "integrity": "sha512-6OiaAaEbLB6dEkRbQyNzFSJv5HDvly3Mc6q/qcPd2uS/g3szR8wAIkh7UndAFKfMypNSTuZ6eCBmgCLR5LacTg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.6.tgz", - "integrity": "sha512-xP5YXbOVRVN8A4pDnSUkEUsL9fYFU6VNhxo8tgr13YnMbf3Pn4xVr+hSyLVjS1Frfi1Uk03ET5Bwml4+0CeYEw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.6.tgz", - "integrity": "sha512-jhH7nJuaOpnTFcuZpWK9dqb6Ge2yGi1okTo0W6wkJrfwAm2vwmO74tF1v07JmrSyHBcKLQATEexclJw9K1Vj7w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.6.tgz", - "integrity": "sha512-olIfZ230B64TvPD6b0tPvrEp2eB0FkyL3KvDlqF4RVmIc/kn3orzXnV6DTQdOOW5UU+M5zKY3/BU47X420/oPw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-codec": "^4.2.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.7.tgz", - "integrity": "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/querystring-builder": "^4.2.6", - "@smithy/types": "^4.10.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-blob-browser": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.7.tgz", - "integrity": "sha512-CIbCTGGX5CI7tfewBPSYD9ycp2Vb2GW5xnXD1n7GcO9mu37EN7A6DvCHM9MX7pOeS1adMn5D+1yRwI3eABVbcA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/chunked-blob-reader": "^5.2.0", - "@smithy/chunked-blob-reader-native": "^4.2.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.6.tgz", - "integrity": "sha512-k3Dy9VNR37wfMh2/1RHkFf/e0rMyN0pjY0FdyY6ItJRjENYyVPRMwad6ZR1S9HFm6tTuIOd9pqKBmtJ4VHxvxg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-stream-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.6.tgz", - "integrity": "sha512-+3T8LkH39YIhYHsv/Ec8lF+92nykZpU+XMBvAyXF/uLcTp86pxa5oSJk1vzaRY9N++qgDLYjzJ6OVbtAgDGwfw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.6.tgz", - "integrity": "sha512-E4t/V/q2T46RY21fpfznd1iSLTvCXKNKo4zJ1QuEFN4SE9gKfu2vb6bgq35LpufkQ+SETWIC7ZAf2GGvTlBaMQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/md5-js": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.6.tgz", - "integrity": "sha512-ZXeh8UmH31JdcNsrQ1o9v1IVuva9JFwxIc6zTMxWX7wcmWvVR7Ai9aUEw5LraNKqdkAsb06clpM2sRH4Iy55Sg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.6.tgz", - "integrity": "sha512-0cjqjyfj+Gls30ntq45SsBtqF3dfJQCeqQPyGz58Pk8OgrAr5YiB7ZvDzjCA94p4r6DCI4qLm7FKobqBjf515w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.0.tgz", - "integrity": "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.19.0", - "@smithy/middleware-serde": "^4.2.7", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "@smithy/url-parser": "^4.2.6", - "@smithy/util-middleware": "^4.2.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.16", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.16.tgz", - "integrity": "sha512-XPpNhNRzm3vhYm7YCsyw3AtmWggJbg1wNGAoqb7NBYr5XA5isMRv14jgbYyUV6IvbTBFZQdf2QpeW43LrRdStQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/service-error-classification": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-retry": "^4.2.6", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.7.tgz", - "integrity": "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.6.tgz", - "integrity": "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.6.tgz", - "integrity": "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.6", - "@smithy/shared-ini-file-loader": "^4.4.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.6.tgz", - "integrity": "sha512-Gsb9jf4ido5BhPfani4ggyrKDd3ZK+vTFWmUaZeFg5G3E5nhFmqiTzAIbHqmPs1sARuJawDiGMGR/nY+Gw6+aQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/querystring-builder": "^4.2.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.6.tgz", - "integrity": "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.6.tgz", - "integrity": "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.6.tgz", - "integrity": "sha512-MeM9fTAiD3HvoInK/aA8mgJaKQDvm8N0dKy6EiFaCfgpovQr4CaOkJC28XqlSRABM+sHdSQXbC8NZ0DShBMHqg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "@smithy/util-uri-escape": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.6.tgz", - "integrity": "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.6.tgz", - "integrity": "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.1.tgz", - "integrity": "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.6.tgz", - "integrity": "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.6", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.1.tgz", - "integrity": "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.19.0", - "@smithy/middleware-endpoint": "^4.4.0", - "@smithy/middleware-stack": "^4.2.6", - "@smithy/protocol-http": "^5.3.6", - "@smithy/types": "^4.10.0", - "@smithy/util-stream": "^4.5.7", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.10.0.tgz", - "integrity": "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.6.tgz", - "integrity": "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.15", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.15.tgz", - "integrity": "sha512-LiZQVAg/oO8kueX4c+oMls5njaD2cRLXRfcjlTYjhIqmwHnCwkQO5B3dMQH0c5PACILxGAQf6Mxsq7CjlDc76A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.18", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.18.tgz", - "integrity": "sha512-Kw2J+KzYm9C9Z9nY6+W0tEnoZOofstVCMTshli9jhQbQCy64rueGfKzPfuFBnVUqZD9JobxTh2DzHmPkp/Va/Q==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.4", - "@smithy/credential-provider-imds": "^4.2.6", - "@smithy/node-config-provider": "^4.3.6", - "@smithy/property-provider": "^4.2.6", - "@smithy/smithy-client": "^4.10.1", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.6.tgz", - "integrity": "sha512-v60VNM2+mPvgHCBXEfMCYrQ0RepP6u6xvbAkMenfe4Mi872CqNkJzgcnQL837e8NdeDxBgrWQRTluKq5Lqdhfg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.6.tgz", - "integrity": "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.6.tgz", - "integrity": "sha512-x7CeDQLPQ9cb6xN7fRJEjlP9NyGW/YeXWc4j/RUhg4I+H60F0PEeRc2c/z3rm9zmsdiMFzpV/rT+4UHW6KM1SA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.2.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.7.tgz", - "integrity": "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.7", - "@smithy/node-http-handler": "^4.4.6", - "@smithy/types": "^4.10.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-waiter": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.6.tgz", - "integrity": "sha512-xU9HwUSik9UUCJmm530yvBy0AwlQFICveKmqvaaTukKkXEAhyiBdHtSrhPrH3rH+uz0ykyaE3LdgsX86C6mDCQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.6", - "@smithy/types": "^4.10.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@spacebarchat/spacebar-webrtc-types": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@spacebarchat/spacebar-webrtc-types/-/spacebar-webrtc-types-1.0.1.tgz", - "integrity": "sha512-WfBRUN2520w7o5vU9HNDug9alNvydQP7H/jwAy8LeHTHwlMMUw/60A54FQDIAtsahw787fR3QZ83UGjhKDzDTg==", - "dev": true, - "license": "AGPL-3.0-only" - }, - "node_modules/@sqltools/formatter": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", - "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", - "license": "MIT" - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@toondepauw/node-zstd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd/-/node-zstd-1.2.0.tgz", - "integrity": "sha512-vVDvPUstF9gKEX+Kqp654bL+4UNus6pkmY8mlcJQpIw/Sn6sO9fl1PEwxB0d0unH5PmVfBUicf1lOXu74OLrCg==", - "license": "MIT", - "engines": { - "node": ">= 14" - }, - "optionalDependencies": { - "@toondepauw/node-zstd-darwin-arm64": "1.2.0", - "@toondepauw/node-zstd-darwin-x64": "1.2.0", - "@toondepauw/node-zstd-freebsd-x64": "1.2.0", - "@toondepauw/node-zstd-linux-arm64-gnu": "1.2.0", - "@toondepauw/node-zstd-linux-arm64-musl": "1.2.0", - "@toondepauw/node-zstd-linux-x64-gnu": "1.2.0", - "@toondepauw/node-zstd-linux-x64-musl": "1.2.0", - "@toondepauw/node-zstd-win32-x64-msvc": "1.2.0" - } - }, - "node_modules/@toondepauw/node-zstd-darwin-arm64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-darwin-arm64/-/node-zstd-darwin-arm64-1.2.0.tgz", - "integrity": "sha512-fdBGlRSrf19M1+IJ98aJCLK6/HKQRnRlhE5i6EZ7kVfD2r6h5Bq9NqHAmQ5MgwtmfB9VMmiIrQAMIxKVeT54pg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 14" - } - }, - "node_modules/@toondepauw/node-zstd-darwin-x64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-darwin-x64/-/node-zstd-darwin-x64-1.2.0.tgz", - "integrity": "sha512-ZVu4zkQUgCQ8z1lLd7/LZzIYDKL48tPguIN8L4SRfLuWWlTq2/4OT6AmchztDbe5soLCDuw64t5X5otDvgsGJA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 14" - } - }, - "node_modules/@toondepauw/node-zstd-freebsd-x64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-freebsd-x64/-/node-zstd-freebsd-x64-1.2.0.tgz", - "integrity": "sha512-kDK+l3uP7htiWvM/klY32siEGIBk3K5Da4/DcXV2q/Vp+Ph86FWpQRzC46wJIpJB60xEU67pxFGLBH/8/RB02g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 14" - } - }, - "node_modules/@toondepauw/node-zstd-linux-arm64-gnu": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-linux-arm64-gnu/-/node-zstd-linux-arm64-gnu-1.2.0.tgz", - "integrity": "sha512-KteUWam7McA0moNiRgu7KSgPu3FE+KXuh3ObqWoco5JK53Sp4Pgr/q1IYsdN6TYYbgJMkJpof62NIbyi6tnVfg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 14" - } - }, - "node_modules/@toondepauw/node-zstd-linux-arm64-musl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-linux-arm64-musl/-/node-zstd-linux-arm64-musl-1.2.0.tgz", - "integrity": "sha512-KjbGOXvPmorhWGD4kEVjdBz/gJgUOw1Eu0xziBwsq03URrI0XSDAY6YiJZpZtGnSjm7XonEpnBZcVwXH1HZz3Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 14" - } - }, - "node_modules/@toondepauw/node-zstd-linux-x64-gnu": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-linux-x64-gnu/-/node-zstd-linux-x64-gnu-1.2.0.tgz", - "integrity": "sha512-qNJDZGd/jplK/1J+8V87dYdrviAxNt1iwI/SQ0JR3HTz5Jdbu+BnVB9oMklCc/bGWa8zTkiZkl/EwA3gtbpo+Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 14" - } - }, - "node_modules/@toondepauw/node-zstd-linux-x64-musl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-linux-x64-musl/-/node-zstd-linux-x64-musl-1.2.0.tgz", - "integrity": "sha512-Cw1qnMXde8caitDMbk0vWrqnVHTSK+JB/Dp3XnZghQjBrD7JLj2qNzh0eTkxNFa+nijbo8HyyslWGswCugYZCw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 14" - } - }, - "node_modules/@toondepauw/node-zstd-win32-x64-msvc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-win32-x64-msvc/-/node-zstd-win32-x64-msvc-1.2.0.tgz", - "integrity": "sha512-7cD2/R7DLZHMxHTsQNtOX3vJCT4qd74a3nWjYYthJ89gqXwwEoC8BwttQxvHd0qCZ3S/cZ/BYoTikXMwMNmCEg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 14" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "devOptional": 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==", - "devOptional": 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==", - "devOptional": 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==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/amqplib": { - "version": "0.10.8", - "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.8.tgz", - "integrity": "sha512-vtDp8Pk1wsE/AuQ8/Rgtm6KUZYqcnTgNvEHwzCkX8rL7AGsC6zqAfKAAJhUZXFhM/Pp++tbnUHiam/8vVpPztA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/bcrypt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "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/cookie-parser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", - "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/express": "*" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "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/i18next-node-fs-backend": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.5.tgz", - "integrity": "sha512-qLuS9SIlAnywXSzCIDi/TTm3DWzLKqLcd5O8K/OSX1Hif6soTu5H+GwZ9Jneoe0gRq6orwaPEe9d0dcmeAFLzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "i18next": ">=17.0.11" - } - }, - "node_modules/@types/json-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", - "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==", - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/module-alias": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/module-alias/-/module-alias-2.0.4.tgz", - "integrity": "sha512-5+G/QXO/DvHZw60FjvbDzO4JmlD/nG5m2/vVGt25VN1eeP3w2bCoks1Wa7VuptMPM1TxJdx6RjO70N9Fw0nZPA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/morgan": { - "version": "1.9.10", - "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", - "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/multer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", - "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/murmurhash-js": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/murmurhash-js/-/murmurhash-js-1.0.6.tgz", - "integrity": "sha512-P2RRwRpevEKO0FrK5xNjxBywg0Br24tEv8K2+iWg56PtcCUNGAkaaOWKBQiUpofA8H/gmgdHXrcvSgp2uXCVAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/needle": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@types/needle/-/needle-3.3.0.tgz", - "integrity": "sha512-UFIuc1gdyzAqeVUYpSL+cliw2MmU/ZUhVZKE7Zo4wPbgc8hbljeKSnn6ls6iG8r5jpegPXLUIhJ+Wb2kLVs8cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "24.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", - "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/nodemailer": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.4.tgz", - "integrity": "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@aws-sdk/client-sesv2": "^3.839.0", - "@types/node": "*" - } - }, - "node_modules/@types/notp": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/notp/-/notp-2.0.5.tgz", - "integrity": "sha512-ZsZS0PYUa6ZE4K3yOGerBvaxCp4ePf6ZmkFbPeilcqz2Ui/lmXox7KlRt7XZkXzqUgXhFLkc09ixyVmFLCU3gQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/probe-image-size": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/@types/probe-image-size/-/probe-image-size-7.2.5.tgz", - "integrity": "sha512-9Bg6d/GNnjmhMMxadDstwrSlquuuLf0jQuPszbU6n3QUfybif3V/ryD3J2i9iaiC5JB/FU/8E41n88SM/UB+Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/needle": "*", - "@types/node": "*" - } - }, - "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.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "node_modules/@types/sharp": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", - "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", - "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/type-utils": "8.50.0", - "@typescript-eslint/utils": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "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.0", - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz", - "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "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.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", - "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.0", - "@typescript-eslint/types": "^8.50.0", - "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.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", - "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0" - }, - "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.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", - "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", - "dev": true, - "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.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", - "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.0", - "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.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", - "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", - "dev": true, - "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.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", - "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.50.0", - "@typescript-eslint/tsconfig-utils": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "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==", - "dev": true, - "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==", - "dev": true, - "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.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz", - "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.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/visitor-keys": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", - "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.50.0", - "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/@typescript-eslint/visitor-keys/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==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/@yukikaze-bot/erlpack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@yukikaze-bot/erlpack/-/erlpack-1.0.1.tgz", - "integrity": "sha512-PCJ2lGCf8DsQtrE411PY+NTsolK48l4InNn1kcBo0iUllKZYGLqeqXEWGA/INrmwanKcoYkU4pBySqUFLQDEoA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.5", - "node-addon-api": "^4.0.0" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC", - "optional": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "optional": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "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==", - "devOptional": true, - "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==", - "dev": true, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "optional": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/amqplib": { - "version": "0.10.9", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.9.tgz", - "integrity": "sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==", - "license": "MIT", - "dependencies": { - "buffer-more-ints": "~1.0.0", - "url-parse": "~1.5.10" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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/ansis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", - "license": "ISC", - "engines": { - "node": ">=14" - } - }, - "node_modules/any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", - "license": "MIT", - "optional": true - }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "license": "ISC", - "optional": true - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": 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==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/asn1js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", - "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", - "license": "BSD-3-Clause", - "dependencies": { - "pvtsutils": "^1.3.6", - "pvutils": "^1.1.3", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/await-to-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", - "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", - "license": "MIT", - "optional": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.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/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "license": "MIT", - "optional": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "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/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/bcrypt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", - "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^8.3.0", - "node-gyp-build": "^4.8.4" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/bcrypt/node_modules/node-addon-api": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", - "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bmp-ts": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", - "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", - "license": "MIT", - "optional": true - }, - "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/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", - "license": "MIT" - }, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/buffer-more-ints": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", - "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", - "license": "MIT" - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "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/bytestreamjs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", - "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cacache/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cbor-extract": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", - "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.1.1" - }, - "bin": { - "download-cbor-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", - "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", - "@cbor-extract/cbor-extract-linux-arm": "2.2.0", - "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", - "@cbor-extract/cbor-extract-linux-x64": "2.2.0", - "@cbor-extract/cbor-extract-win32-x64": "2.2.0" - } - }, - "node_modules/cbor-x": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.0.tgz", - "integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==", - "license": "MIT", - "optionalDependencies": { - "cbor-extract": "^2.2.0" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cheerio": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", - "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.0.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.12.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "optional": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC", - "optional": true - }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "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==", - "devOptional": 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/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "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/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT", - "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/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/discord-protos": { - "version": "1.2.90", - "resolved": "https://registry.npmjs.org/discord-protos/-/discord-protos-1.2.90.tgz", - "integrity": "sha512-7W57dYO3ohAseS2k46UpGq+1b8FDuXIRz0WlxoWI6cgESuIPI6DCXfwc3tDeEaps81kfyOnBrT0LdNX+TrE/oQ==", - "license": "MIT", - "dependencies": { - "@protobuf-ts/runtime": "^2.9.6" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "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/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "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/email-providers": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/email-providers/-/email-providers-2.19.0.tgz", - "integrity": "sha512-HwDo7NLmBWXHtnXlediIbI1x+WSgTszWi35TZxlGhW49iW/dZkzC78f4RD/8osLY2Rqh62sgD2ufduKRO54Raw==", - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "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/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/encoding-sniffer/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/encoding/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", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "license": "MIT", - "optional": true - }, - "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/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "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": "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==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "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/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "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/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==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "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/espree/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==", - "dev": true, - "license": "Apache-2.0", - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "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/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/exif-be-gone": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/exif-be-gone/-/exif-be-gone-1.5.1.tgz", - "integrity": "sha512-+fV9PoomNVR5Hmp0n1c0ZVl78/GaFrpnC0t7q4F9Aey8NcL+7Lutcez8KY2Ni30NWpvgLXawqiRFFwtdo4QgFg==", - "license": "ISC", - "bin": { - "exif-be-gone": "cli.js" - } - }, - "node_modules/exif-parser": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", - "optional": true - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.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/express/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/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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fast-zlib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-zlib/-/fast-zlib-2.0.1.tgz", - "integrity": "sha512-DCoYgNagM2Bt1VIpXpdGnRx4LzqJeYG0oh6Nf/7cWo6elTXkFGMw9CrRCYYUIapYNrozYMoyDRflx9mgT3Awyw==", - "license": "MIT", - "funding": { - "type": "patreon", - "url": "https://patreon.com/timotejroiko" - } - }, - "node_modules/fido2-lib": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/fido2-lib/-/fido2-lib-3.5.5.tgz", - "integrity": "sha512-XEXa+kRyQEq4Yse5bG+4rrvsxfOMp+ihzI2Uxi0/6OJ4ZaUdNKl8fBGDoyFZu/TKjCJGcL182/jf+UoyMSf9ow==", - "license": "MIT", - "dependencies": { - "@hexagon/base64": "~1.1.28", - "@peculiar/webcrypto": "~1.4.6", - "asn1js": "~3.0.6", - "cbor-x": "~1.6.0", - "jose": "^6.1.0", - "pkijs": "~3.3.2", - "punycode.js": "^2.3.1", - "tldts": "~7.0.17" - }, - "engines": { - "node": ">=10" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/file-type": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.1.tgz", - "integrity": "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==", - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "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": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "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==", - "dev": true, - "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/find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "micromatch": "^4.0.2" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "optional": true, - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true, - "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/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "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/gifwrap": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", - "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", - "license": "MIT", - "optional": true, - "dependencies": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "devOptional": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "devOptional": true, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "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/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC", - "optional": true - }, - "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/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause", - "optional": true - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/i18next": { - "version": "25.7.3", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.3.tgz", - "integrity": "sha512-2XaT+HpYGuc2uTExq9TVRhLsso+Dxym6PWaKpn36wfBmTI779OQ7iP/XaZHzrnGyzU4SHpFrTYLKfVyBfAhVNA==", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.28.4" - }, - "peerDependencies": { - "typescript": "^5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/i18next-fs-backend": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.6.1.tgz", - "integrity": "sha512-eYWTX7QT7kJ0sZyCPK6x1q+R63zvNKv2D6UdbMf15A8vNb2ZLyw4NNNZxPFwXlIv/U+oUtg8SakW6ZgJZcoqHQ==", - "license": "MIT" - }, - "node_modules/i18next-http-middleware": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.9.0.tgz", - "integrity": "sha512-hM56al/hnQ8a6Us5uRvw/en1gSI3vePqBUa9nX4+ISvIHOU/li7ffu11FrI2aR83nphTe6ofo7mxhVu0PLAe2w==", - "license": "MIT" - }, - "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/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "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": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-q": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", - "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "16.9.1" - } - }, - "node_modules/image-q/node_modules/@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", - "license": "MIT", - "optional": true - }, - "node_modules/image-size": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", - "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", - "license": "MIT", - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "license": "ISC", - "optional": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "devOptional": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "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/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "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-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "license": "MIT", - "optional": true - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.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/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "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/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jimp": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", - "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/diff": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-gif": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-blur": "1.6.0", - "@jimp/plugin-circle": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-contain": "1.6.0", - "@jimp/plugin-cover": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-displace": "1.6.0", - "@jimp/plugin-dither": "1.6.0", - "@jimp/plugin-fisheye": "1.6.0", - "@jimp/plugin-flip": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/plugin-mask": "1.6.0", - "@jimp/plugin-print": "1.6.0", - "@jimp/plugin-quantize": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/plugin-rotate": "1.6.0", - "@jimp/plugin-threshold": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "license": "BSD-3-Clause", - "optional": true - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-stable-stringify": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", - "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "isarray": "^2.0.5", - "jsonify": "^0.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", - "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", - "dev": true, - "license": "Public Domain", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/klaw-sync": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mailgun.js": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-12.4.0.tgz", - "integrity": "sha512-3Mwvwo02PUmFkW0zxSwvTjHBFCaMLb903RuQaEkTU8Z8medItb7Ey0K3XXLVDErArbEjPoRRqxPDdzWqXaokfQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "axios": "^1.12.1", - "base-64": "^1.0.0", - "url-join": "^4.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "license": "MIT", - "optional": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "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==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "license": "ISC", - "optional": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "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/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "license": "MIT", - "optional": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/module-alias": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", - "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", - "license": "MIT" - }, - "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", - "license": "MIT", - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.1.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "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/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" - }, - "engines": { - "node": ">= 10.16.0" - } - }, - "node_modules/multer/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/multer/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/multer/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/multer/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/murmurhash-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", - "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", - "license": "MIT" - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/needle": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", - "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", - "license": "MIT", - "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/needle/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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/node-2fa": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-2fa/-/node-2fa-2.0.3.tgz", - "integrity": "sha512-PQldrOhjuoZyoydMvMSctllPN1ZPZ1/NwkEcgYwY9faVqE/OymxR+3awPpbWZxm6acLKqvmNqQmdqTsqYyflFw==", - "license": "Apache-2.0", - "dependencies": { - "@types/notp": "^2.0.0", - "notp": "^2.0.3", - "thirty-two": "1.0.2", - "tslib": "^2.1.0" - } - }, - "node_modules/node-abi": { - "version": "3.85.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", - "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", - "license": "MIT", - "optional": true - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "license": "MIT", - "optional": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", - "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-mailjet": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/node-mailjet/-/node-mailjet-6.0.11.tgz", - "integrity": "sha512-5UaawWhpJc1uFxUl9a07jE8a4ZOax5hj7XYC0LFYyWOP8Rp0I8XvjbiQ6gy/ns7KSw0hqmjytP85GQsdVGEx5g==", - "license": "MIT", - "optional": true, - "dependencies": { - "axios": "^1.12.0", - "json-bigint": "^1.0.0", - "url-join": "^4.0.0" - }, - "engines": { - "node": ">= 12.0.0", - "npm": ">= 6.9.0" - } - }, - "node_modules/nodemailer": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", - "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", - "license": "MIT-0", - "optional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/notp": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz", - "integrity": "sha512-oBig/2uqkjQ5AkBuw4QJYwkEWa/q+zHxI5/I5z6IeP2NT0alpJFsP/trrfCC+9xOAgQSZXssNi962kp5KBmypQ==", - "engines": { - "node": "> v0.6.0" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", - "license": "MIT", - "optional": true - }, - "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/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "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/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)", - "optional": true - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-bmfont-ascii": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", - "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", - "license": "MIT", - "optional": true - }, - "node_modules/parse-bmfont-binary": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", - "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", - "license": "MIT", - "optional": true - }, - "node_modules/parse-bmfont-xml": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", - "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", - "license": "MIT", - "optional": true, - "dependencies": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.5.0" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "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/patch-package": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", - "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@yarnpkg/lockfile": "^1.1.0", - "chalk": "^4.1.2", - "ci-info": "^3.7.0", - "cross-spawn": "^7.0.3", - "find-yarn-workspace-root": "^2.0.0", - "fs-extra": "^10.0.0", - "json-stable-stringify": "^1.0.2", - "klaw-sync": "^6.0.0", - "minimist": "^1.2.6", - "open": "^7.4.2", - "semver": "^7.5.3", - "slash": "^2.0.0", - "tmp": "^0.2.4", - "yaml": "^2.2.2" - }, - "bin": { - "patch-package": "index.js" - }, - "engines": { - "node": ">=14", - "npm": ">5" - } - }, - "node_modules/patch-package/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "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/path-equal": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", - "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "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/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/pg": { - "version": "8.16.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", - "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", - "license": "MIT", - "peer": true, - "dependencies": { - "pg-connection-string": "^2.9.1", - "pg-pool": "^3.10.1", - "pg-protocol": "^1.10.3", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.2.7" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", - "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", - "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", - "license": "MIT" - }, - "node_modules/pg-cursor": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.15.3.tgz", - "integrity": "sha512-eHw63TsiGtFEfAd7tOTZ+TLy+i/2ePKS20H84qCQ+aQ60pve05Okon9tKMC+YN3j6XyeFoHnaim7Lt9WVafQsA==", - "license": "MIT", - "peerDependencies": { - "pg": "^8" - } - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", - "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", - "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", - "license": "MIT" - }, - "node_modules/pg-query-stream": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/pg-query-stream/-/pg-query-stream-4.10.3.tgz", - "integrity": "sha512-h2utrzpOIzeT9JfaqfvBbVuvCfBjH86jNfVrGGTbyepKAIOyTfDew0lAt8bbJjs9n/I5bGDl7S2sx6h5hPyJxw==", - "license": "MIT", - "peer": true, - "dependencies": { - "pg-cursor": "^2.15.3" - }, - "peerDependencies": { - "pg": "^8" - } - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", - "license": "ISC", - "optional": true, - "dependencies": { - "pngjs": "^6.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pixelmatch/node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/pkijs": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", - "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", - "license": "BSD-3-Clause", - "dependencies": { - "@noble/hashes": "1.4.0", - "asn1js": "^3.0.6", - "bytestreamjs": "^2.0.1", - "pvtsutils": "^1.3.6", - "pvutils": "^1.1.3", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/pngjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", - "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14.19.0" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-quick": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-4.2.2.tgz", - "integrity": "sha512-uAh96tBW1SsD34VhhDmWuEmqbpfYc/B3j++5MC/6b3Cb8Ow7NJsvKFhg0eoGu2xXX+o9RkahkTK6sUdd8E7g5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.7", - "ignore": "^7.0.5", - "mri": "^1.2.0", - "picocolors": "^1.1.1", - "picomatch": "^4.0.2", - "tinyexec": "^0.3.2", - "tslib": "^2.8.1" - }, - "bin": { - "pretty-quick": "lib/cli.mjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://opencollective.com/pretty-quick" - }, - "peerDependencies": { - "prettier": "^3.0.0" - } - }, - "node_modules/pretty-quick/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/pretty-quick/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/probe-image-size": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz", - "integrity": "sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==", - "license": "MIT", - "dependencies": { - "lodash.merge": "^4.6.2", - "needle": "^2.5.2", - "stream-parser": "~0.3.1" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "license": "ISC", - "optional": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "license": "MIT", - "optional": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT", - "optional": true - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pvtsutils": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", - "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.1" - } - }, - "node_modules/pvutils": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", - "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "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/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "license": "MIT" - }, - "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.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", - "license": "MIT", - "optional": true, - "dependencies": { - "readable-stream": "^4.7.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "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", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "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/sax": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", - "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", - "license": "BlueOak-1.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.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC", - "optional": true - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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/sha.js": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", - "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", - "license": "(MIT AND BSD-3-Clause)", - "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1", - "to-buffer": "^1.2.0" - }, - "bin": { - "sha.js": "bin.js" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC", - "optional": true - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "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/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "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", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-xml-to-json": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz", - "integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20.12.2" - } - }, - "node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "optional": true, - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sql-highlight": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", - "integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==", - "funding": [ - "https://github.com/scriptcoded/sql-highlight?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/scriptcoded" - } - ], - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/sqlite3": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", - "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1", - "tar": "^6.1.11" - }, - "optionalDependencies": { - "node-gyp": "8.x" - }, - "peerDependencies": { - "node-gyp": "8.x" - }, - "peerDependenciesMeta": { - "node-gyp": { - "optional": true - } - } - }, - "node_modules/sqlite3/node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "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/stream-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", - "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", - "license": "MIT", - "dependencies": { - "debug": "2" - } - }, - "node_modules/stream-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/stream-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", - "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/thirty-two": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", - "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", - "engines": { - "node": ">=0.2.6" - } - }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "license": "MIT", - "optional": true - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "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/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tldts": { - "version": "7.0.19", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", - "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.19" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "7.0.19", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", - "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", - "license": "MIT" - }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.14" - } - }, - "node_modules/to-buffer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", - "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", - "license": "MIT", - "dependencies": { - "isarray": "^2.0.5", - "safe-buffer": "^5.2.1", - "typed-array-buffer": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "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/token-types": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", - "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.1.0", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "optional": true - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "peer": true, - "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/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "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==", - "dev": true, - "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/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/typeorm": { - "version": "0.3.28", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", - "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", - "license": "MIT", - "dependencies": { - "@sqltools/formatter": "^1.2.5", - "ansis": "^4.2.0", - "app-root-path": "^3.1.0", - "buffer": "^6.0.3", - "dayjs": "^1.11.19", - "debug": "^4.4.3", - "dedent": "^1.7.0", - "dotenv": "^16.6.1", - "glob": "^10.5.0", - "reflect-metadata": "^0.2.2", - "sha.js": "^2.4.12", - "sql-highlight": "^6.1.0", - "tslib": "^2.8.1", - "uuid": "^11.1.0", - "yargs": "^17.7.2" - }, - "bin": { - "typeorm": "cli.js", - "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", - "typeorm-ts-node-esm": "cli-ts-node-esm.js" - }, - "engines": { - "node": ">=16.13.0" - }, - "funding": { - "url": "https://opencollective.com/typeorm" - }, - "peerDependencies": { - "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "@sap/hana-client": "^2.14.22", - "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", - "ioredis": "^5.0.4", - "mongodb": "^5.8.0 || ^6.0.0", - "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", - "mysql2": "^2.2.5 || ^3.0.1", - "oracledb": "^6.3.0", - "pg": "^8.5.1", - "pg-native": "^3.0.0", - "pg-query-stream": "^4.0.0", - "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", - "sql.js": "^1.4.0", - "sqlite3": "^5.0.3", - "ts-node": "^10.7.0", - "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" - }, - "peerDependenciesMeta": { - "@google-cloud/spanner": { - "optional": true - }, - "@sap/hana-client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mssql": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "oracledb": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-native": { - "optional": true - }, - "pg-query-stream": { - "optional": true - }, - "redis": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "ts-node": { - "optional": true - }, - "typeorm-aurora-data-api-driver": { - "optional": true - } - } - }, - "node_modules/typeorm/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/typeorm/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "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", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/typeorm/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/typeorm/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/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/typeorm/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-json-schema": { - "version": "0.65.1", - "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.65.1.tgz", - "integrity": "sha512-tuGH7ff2jPaUYi6as3lHyHcKpSmXIqN7/mu50x3HlYn0EHzLpmt3nplZ7EuhUkO0eqDRc9GqWNkfjgBPIS9kxg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/node": "^18.11.9", - "glob": "^7.1.7", - "path-equal": "^1.2.5", - "safe-stable-stringify": "^2.2.0", - "ts-node": "^10.9.1", - "typescript": "~5.5.0", - "yargs": "^17.1.1" - }, - "bin": { - "typescript-json-schema": "bin/typescript-json-schema" - } - }, - "node_modules/typescript-json-schema/node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/typescript-json-schema/node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-json-schema/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", - "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "license": "ISC", - "optional": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "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==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "license": "MIT", - "optional": true - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/utif2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", - "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", - "license": "MIT", - "optional": true, - "dependencies": { - "pako": "^1.0.11" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "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==", - "devOptional": 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/webcrypto-core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", - "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.3.13", - "@peculiar/json-schema": "^1.1.12", - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.7.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "optional": true - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-encoding/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/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "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/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "optional": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "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/wretch": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/wretch/-/wretch-2.11.1.tgz", - "integrity": "sha512-rJejp30zE8Igs4Mvo4pxXaiOKdSIGYLxyVaY+jZTlAMfQyi/hZsDwMRa9R09N0iS9Q8cLvplKfHwwyBv6XRWsg==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "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/xml-parse-from-string": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", - "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", - "license": "MIT", - "optional": true - }, - "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "license": "MIT", - "optional": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": 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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } + "name": "spacebar-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "spacebar-server", + "version": "1.0.0", + "hasInstallScript": true, + "license": "AGPL-3.0-only", + "dependencies": { + "@aws-sdk/client-s3": "^3.953.0", + "@toondepauw/node-zstd": "^1.2.0", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "amqplib": "^0.10.9", + "bcrypt": "^6.0.0", + "body-parser": "^2.2.1", + "chalk": "^5.6.2", + "cheerio": "^1.1.2", + "cookie-parser": "^1.4.7", + "discord-protos": "^1.2.90", + "dotenv": "^17.2.3", + "email-providers": "^2.19.0", + "exif-be-gone": "^1.5.1", + "express": "^5.2.1", + "fast-zlib": "^2.0.1", + "fido2-lib": "^3.5.5", + "file-type": "^21.1.1", + "form-data": "^4.0.5", + "i18next": "^25.7.3", + "i18next-fs-backend": "^2.6.1", + "i18next-http-middleware": "^3.9.0", + "image-size": "^2.0.2", + "json-bigint": "^1.0.0", + "jsonwebtoken": "^9.0.3", + "module-alias": "^2.2.3", + "morgan": "^1.10.1", + "ms": "^2.1.3", + "multer": "^2.0.2", + "murmurhash-js": "^1.0.0", + "node-2fa": "^2.0.3", + "pg": "^8.16.3", + "pg-query-stream": "^4.10.3", + "picocolors": "^1.1.1", + "probe-image-size": "^7.2.3", + "reflect-metadata": "^0.2.2", + "sqlite3": "^5.1.7", + "tslib": "^2.8.1", + "typeorm": "^0.3.28", + "wretch": "^2.11.1", + "ws": "^8.18.3" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^9.14.0", + "@spacebarchat/spacebar-webrtc-types": "^1.0.1", + "@types/amqplib": "^0.10.8", + "@types/bcrypt": "^6.0.0", + "@types/body-parser": "^1.19.6", + "@types/cookie-parser": "^1.4.10", + "@types/express": "^5.0.6", + "@types/i18next-node-fs-backend": "^2.1.5", + "@types/json-bigint": "^1.0.4", + "@types/jsonwebtoken": "^9.0.10", + "@types/module-alias": "^2.0.4", + "@types/morgan": "^1.9.10", + "@types/multer": "^2.0.0", + "@types/murmurhash-js": "^1.0.6", + "@types/node": "^24.10.4", + "@types/nodemailer": "^7.0.4", + "@types/probe-image-size": "^7.2.5", + "@types/sharp": "^0.31.1", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^8.50.0", + "@typescript-eslint/parser": "^8.50.0", + "eslint": "^9.39.2", + "globals": "^16.5.0", + "husky": "^9.1.7", + "patch-package": "^8.0.1", + "prettier": "^3.7.4", + "pretty-quick": "^4.2.2", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-json-schema": "^0.65.1" + }, + "optionalDependencies": { + "@sendgrid/mail": "^8.1.6", + "@yukikaze-bot/erlpack": "^1.0.1", + "jimp": "^1.6.0", + "mailgun.js": "^12.4.0", + "node-mailjet": "^6.0.11", + "nodemailer": "^7.0.11" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.953.0.tgz", + "integrity": "sha512-Lxxdhq5nt6ONulu1UHbIS0tVIar7itXv1m4TJfkVzuSm/yQzxIwnFkLtgW/0P5KIE+FS1yUoE2lS+dJBS1PLFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.953.0", + "@aws-sdk/credential-provider-node": "3.953.0", + "@aws-sdk/middleware-bucket-endpoint": "3.953.0", + "@aws-sdk/middleware-expect-continue": "3.953.0", + "@aws-sdk/middleware-flexible-checksums": "3.953.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-location-constraint": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-sdk-s3": "3.953.0", + "@aws-sdk/middleware-ssec": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.953.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/signature-v4-multi-region": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.953.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/eventstream-serde-browser": "^4.2.6", + "@smithy/eventstream-serde-config-resolver": "^4.3.6", + "@smithy/eventstream-serde-node": "^4.2.6", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-blob-browser": "^4.2.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/hash-stream-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/md5-js": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.3.15", + "@smithy/middleware-retry": "^4.4.15", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.0", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.14", + "@smithy/util-defaults-mode-node": "^4.2.17", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-stream": "^4.5.7", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.953.0.tgz", + "integrity": "sha512-8GkTGBfFB6UbaGpXt2ZgYql+prCYLBQeSQYGb+vBZ8TnU6L6badhC8g9QkCYO2ul6rm02+MBrFgcNx40NSAsoA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.953.0", + "@aws-sdk/credential-provider-node": "3.953.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.953.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/signature-v4-multi-region": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.953.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.3.15", + "@smithy/middleware-retry": "^4.4.15", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.0", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.14", + "@smithy/util-defaults-mode-node": "^4.2.17", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.953.0.tgz", + "integrity": "sha512-WU8XtORGnhy1JjTLflcGcNGU329pmwl/2claTVGp7vrgUKyreQV9Ke3BkMuUPYLOGO/Znstzc1VbUT9ORH3+WQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.953.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.953.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.953.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.3.15", + "@smithy/middleware-retry": "^4.4.15", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.0", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.14", + "@smithy/util-defaults-mode-node": "^4.2.17", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.953.0.tgz", + "integrity": "sha512-hnz4ukn+XupuotZcFAyCIX41QqEWSHc8zf4gN4l5qVP2M8YCyZkLLZ5t5LaWLJkUFbOUU2w4SuGpUp+5ddtSkw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@aws-sdk/xml-builder": "3.953.0", + "@smithy/core": "^3.19.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/signature-v4": "^5.3.6", + "@smithy/smithy-client": "^4.10.0", + "@smithy/types": "^4.10.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.953.0.tgz", + "integrity": "sha512-ahOxDxvaKUf99LU9iRmRtPW2HLmsR+ix2WyzRGl1PpLltiRLMbKJHj5Tu2xyOsgLxs8tefK9D1j0SUgPk8GJ6g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.953.0.tgz", + "integrity": "sha512-kYQEcJpJLiT+1ZdsQDMrIs2o3YydxdAIDdLUxbIwks1+WQz2sCr9b94ld19aMZ0rejosASO0J3dfY0lt3Tfl+Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.0", + "@smithy/types": "^4.10.0", + "@smithy/util-stream": "^4.5.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.953.0.tgz", + "integrity": "sha512-5cy6b3VntBvhRCanwohRtR0wbKKUd6JfIsnuXImB4JcZa2iMW5tDW0LhNangYZ9wrrM7sJol6cKHtUW9LNemFQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/credential-provider-env": "3.953.0", + "@aws-sdk/credential-provider-http": "3.953.0", + "@aws-sdk/credential-provider-login": "3.953.0", + "@aws-sdk/credential-provider-process": "3.953.0", + "@aws-sdk/credential-provider-sso": "3.953.0", + "@aws-sdk/credential-provider-web-identity": "3.953.0", + "@aws-sdk/nested-clients": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/credential-provider-imds": "^4.2.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.953.0.tgz", + "integrity": "sha512-Drf4v7uBKnU/nY/qgQD9ZA2zD5gjQEatxQajQHUN54erDSQ2rB5ApNuTnc2cyNwaxURnQvO1J72mwO6P5lIpsg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/nested-clients": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.953.0.tgz", + "integrity": "sha512-/3gUap/QwAV/U3RPStcSjdiksDboTdcz82qajfhURhtAe9iEdoKJy7vTYqg75eX7IjpgBwLGajt0URasUofrPg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.953.0", + "@aws-sdk/credential-provider-http": "3.953.0", + "@aws-sdk/credential-provider-ini": "3.953.0", + "@aws-sdk/credential-provider-process": "3.953.0", + "@aws-sdk/credential-provider-sso": "3.953.0", + "@aws-sdk/credential-provider-web-identity": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/credential-provider-imds": "^4.2.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.953.0.tgz", + "integrity": "sha512-YeqzqxzBzXnqdzn4pBuoXeCmWrXOLIu5owAowUc4dOvHmtlpXxB3beJdQ9g/Ky6fG6iaAPKO1vrCEoHNjM85Dw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.953.0.tgz", + "integrity": "sha512-Kp/49vAJweixMo3q85eVpq1JO9KgKc6A+f8/8WDkN5CkMsfKQcGJZbO5JtuJfzar4pPmRKbgOeOP1qQsOmfyfw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.953.0", + "@aws-sdk/core": "3.953.0", + "@aws-sdk/token-providers": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.953.0.tgz", + "integrity": "sha512-V8SpGVtNjQZboHjb/GLM4L3/6O/AZJFtwJ8fiBaemKMTntl8haioZlQHDklWixR9s9zthOdFsCDs1+92ndUBRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/nested-clients": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.953.0.tgz", + "integrity": "sha512-YHVRIOowtGIl/L2WuS83FgRlm31tU0aL1yryWaFtF+AFjA5BIeiFkxIZqaRGxJpJvFEBdohsyq6Ipv5mgWfezg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-arn-parser": "3.953.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.953.0.tgz", + "integrity": "sha512-BQTVXrypQ0rbb7au/Hk4IS5GaJZlwk6O44Rjk6Kxb0IvGQhSurNTuesFiJx1sLbf+w+T31saPtODcfQQERqhCQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.953.0.tgz", + "integrity": "sha512-wpCoYg2ok0oSOupFiSPPiXvAhnXce2FkqMt/Bao+kyG3yx26NVG/4/PBftTDVCsJwuDvX/Fw8EuwbAXVmmYLCg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-stream": "^4.5.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.953.0.tgz", + "integrity": "sha512-jTGhfkONav+r4E6HLOrl5SzBqDmPByUYCkyB/c/3TVb8jX3wAZx8/q9bphKpCh+G5ARi3IdbSisgkZrJYqQ19Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.953.0.tgz", + "integrity": "sha512-h0urrbteIQEybyIISaJfQLZ/+/lJPRzPWAQT4epvzfgv/4MKZI7K83dK7SfTwAooVKFBHiCMok2Cf0iHDt07Kw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.953.0.tgz", + "integrity": "sha512-PlWdVYgcuptkIC0ZKqVUhWNtSHXJSx7U9V8J7dJjRmsXC40X7zpEycvrkzDMJjeTDGcCceYbyYAg/4X1lkcIMw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.953.0.tgz", + "integrity": "sha512-cmIJx0gWeesUKK4YwgE+VQL3mpACr3/J24fbwnc1Z5tntC86b+HQFzU5vsBDw6lLwyD46dBgWdsXFh1jL+ZaFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.953.0.tgz", + "integrity": "sha512-qubXS4GeDdoF7nqq2KEqW6jG7OjifwNfb/m6o5b6o7ZF2OSBr1MqyLykDWqnLjLI4NmM4qRPl5oTDGV0kBqoDA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-arn-parser": "3.953.0", + "@smithy/core": "^3.19.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/signature-v4": "^5.3.6", + "@smithy/smithy-client": "^4.10.0", + "@smithy/types": "^4.10.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-stream": "^4.5.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.953.0.tgz", + "integrity": "sha512-OrhG1kcQ9zZh3NS3RovR028N0+UndQ957zF1k5HPLeFLwFwQN1uPOufzzPzAyXIIKtR69ARFsQI4mstZS4DMvw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.953.0.tgz", + "integrity": "sha512-xZSU54cEHlIvRTUewyTAajb4WJPdbBd1mIaDYOzac07+vsfMuCREQ5CheQ3inoBfqske7QOX+mIjLpVV4azAnw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@smithy/core": "^3.19.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.953.0.tgz", + "integrity": "sha512-YEBI0b/4ezNkw5W4+RBRUoueFzqEPPrnkh/3LBqeJ7RWZTymBhxlpoLo6Gfxw9LxtDgv2vZ5K5rgC4/6Ly8ADg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.953.0", + "@aws-sdk/middleware-host-header": "3.953.0", + "@aws-sdk/middleware-logger": "3.953.0", + "@aws-sdk/middleware-recursion-detection": "3.953.0", + "@aws-sdk/middleware-user-agent": "3.953.0", + "@aws-sdk/region-config-resolver": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@aws-sdk/util-endpoints": "3.953.0", + "@aws-sdk/util-user-agent-browser": "3.953.0", + "@aws-sdk/util-user-agent-node": "3.953.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/core": "^3.19.0", + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/hash-node": "^4.2.6", + "@smithy/invalid-dependency": "^4.2.6", + "@smithy/middleware-content-length": "^4.2.6", + "@smithy/middleware-endpoint": "^4.3.15", + "@smithy/middleware-retry": "^4.4.15", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/smithy-client": "^4.10.0", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.14", + "@smithy/util-defaults-mode-node": "^4.2.17", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.953.0.tgz", + "integrity": "sha512-5MJgnsc+HLO+le0EK1cy92yrC7kyhGZSpaq8PcQvKs9qtXCXT5Tb6tMdkr5Y07JxYsYOV1omWBynvL6PWh08tQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/config-resolver": "^4.4.4", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.953.0.tgz", + "integrity": "sha512-9xDZTrUveWHLJDiXWW7UdrjtdoqlWijQlxIWRMAlxruqdXJDSRkn6taSAs7fGRwHJfxjiDsn84Fcqwx5Lw55zg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/signature-v4": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.953.0.tgz", + "integrity": "sha512-4FWWR3lDDuIftmCEWpGejfkJu2J1VG2MUktChQshADXABfVfM0fsT7OYlO7Sy106nOe9upE4uQTWXg9YT/6ssw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.953.0", + "@aws-sdk/nested-clients": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.953.0.tgz", + "integrity": "sha512-M9Iwg9kTyqTErI0vOTVVpcnTHWzS3VplQppy8MuL02EE+mJ0BIwpWfsaAPQW+/XnVpdNpWZTsHcNE29f1+hR8g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.953.0.tgz", + "integrity": "sha512-9hqdKkn4OvYzzaLryq2xnwcrPc8ziY34i9szUdgBfSqEC6pBxbY9/lLXmrgzfwMSL2Z7/v2go4Od0p5eukKLMQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.953.0.tgz", + "integrity": "sha512-rjaS6jrFksopXvNg6YeN+D1lYwhcByORNlFuYesFvaQNtPOufbE5tJL4GJ3TMXyaY0uFR28N5BHHITPyWWfH/g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-endpoints": "^3.2.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.953.0.tgz", + "integrity": "sha512-mPxK+I1LcrgC/RSa3G5AMAn8eN2Ay0VOgw8lSRmV1jCtO+iYvNeCqOdxoJUjOW6I5BA4niIRWqVORuRP07776Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.953.0.tgz", + "integrity": "sha512-UF5NeqYesWuFao+u7LJvpV1SJCaLml5BtFZKUdTnNNMeN6jvV+dW/eQoFGpXF94RCqguX0XESmRuRRPQp+/rzQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.953.0", + "@smithy/types": "^4.10.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.953.0.tgz", + "integrity": "sha512-HjJ0Nq/57kg0QCjt8mwBAK6C34njKezy9ItUNcrWITzrBZv7ikQtyqM1pindAIzgLaBb7ly/0kbJqMY+NPbcJA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.953.0", + "@aws-sdk/types": "3.953.0", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.953.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.953.0.tgz", + "integrity": "sha512-Zmrj21jQ2OeOJGr9spPiN00aQvXa/WUqRXcTVENhrMt+OFoSOfDFpYhUj9NQ09QmQ8KMWFoWuWW6iKurNqLvAA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", + "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", + "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-darwin-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", + "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", + "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", + "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", + "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-win32-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", + "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "dev": true, + "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/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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@hexagon/base64": { + "version": "1.1.28", + "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", + "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jimp/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", + "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/file-ops": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "await-to-js": "^3.0.0", + "exif-parser": "^0.1.12", + "file-type": "^16.0.0", + "mime": "3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/core/node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "optional": true, + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@jimp/core/node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@jimp/core/node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@jimp/diff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", + "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "pixelmatch": "^5.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/file-ops": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", + "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-bmp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", + "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "bmp-ts": "^1.0.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", + "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", + "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", + "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", + "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", + "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", + "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", + "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", + "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", + "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", + "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", + "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", + "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", + "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/types": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", + "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", + "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", + "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "any-base": "^1.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", + "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", + "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/types": "1.6.0", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "simple-xml-to-json": "^1.2.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", + "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", + "license": "MIT", + "optional": true, + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", + "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", + "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", + "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", + "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", + "license": "MIT", + "optional": true, + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/types": "1.6.0", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "devOptional": 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==", + "devOptional": 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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.4.6.tgz", + "integrity": "sha512-YBcMfqNSwn3SujUJvAaySy5tlYbYm6tVt9SKoXu8BaTdKGROiJDgPR3TXpZdAKUfklzm3lRapJEAltiMQtBgZg==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2", + "webcrypto-core": "^1.7.9" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@protobuf-ts/runtime": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz", + "integrity": "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@sendgrid/client": { + "version": "8.1.6", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.6.tgz", + "integrity": "sha512-/BHu0hqwXNHr2aLhcXU7RmmlVqrdfrbY9KpaNj00KZHlVOVoRxRVrpOCabIB+91ISXJ6+mLM9vpaVUhK6TwBWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.12.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "license": "MIT", + "optional": true, + "dependencies": { + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@sendgrid/mail": { + "version": "8.1.6", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.6.tgz", + "integrity": "sha512-/ZqxUvKeEztU9drOoPC/8opEPOk+jLlB2q4+xpx6HVLq6aFu3pMpalkTpAQz8XfRfpLp8O25bh6pGPcHDCYpqg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@sendgrid/client": "^8.1.5", + "@sendgrid/helpers": "^8.0.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.6.tgz", + "integrity": "sha512-P7JD4J+wxHMpGxqIg6SHno2tPkZbBUBLbPpR5/T1DEUvw/mEaINBMaPFZNM7lA+ToSCZ36j6nMHa+5kej+fhGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", + "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", + "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.4.tgz", + "integrity": "sha512-s3U5ChS21DwU54kMmZ0UJumoS5cg0+rGVZvN6f5Lp6EbAVi0ZyP+qDSHdewfmXKUgNK1j3z45JyzulkDukrjAA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.6", + "@smithy/util-middleware": "^4.2.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.19.0.tgz", + "integrity": "sha512-Y9oHXpBcXQgYHOcAEmxjkDilUbSTkgKjoHYed3WaYUH8jngq8lPWDBSpjHblJ9uOgBdy5mh3pzebrScDdYr29w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.7", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-stream": "^4.5.7", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.6.tgz", + "integrity": "sha512-xBmawExyTzOjbhzkZwg+vVm/khg28kG+rj2sbGlULjFd1jI70sv/cbpaR0Ev4Yfd6CpDUDRMe64cTqR//wAOyA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.6.tgz", + "integrity": "sha512-OZfsI+YRG26XZik/jKMMg37acnBSbUiK/8nETW3uM3mLj+0tMmFXdHQw1e5WEd/IHN8BGOh3te91SNDe2o4RHg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.10.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.6.tgz", + "integrity": "sha512-6OiaAaEbLB6dEkRbQyNzFSJv5HDvly3Mc6q/qcPd2uS/g3szR8wAIkh7UndAFKfMypNSTuZ6eCBmgCLR5LacTg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.6.tgz", + "integrity": "sha512-xP5YXbOVRVN8A4pDnSUkEUsL9fYFU6VNhxo8tgr13YnMbf3Pn4xVr+hSyLVjS1Frfi1Uk03ET5Bwml4+0CeYEw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.6.tgz", + "integrity": "sha512-jhH7nJuaOpnTFcuZpWK9dqb6Ge2yGi1okTo0W6wkJrfwAm2vwmO74tF1v07JmrSyHBcKLQATEexclJw9K1Vj7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.6.tgz", + "integrity": "sha512-olIfZ230B64TvPD6b0tPvrEp2eB0FkyL3KvDlqF4RVmIc/kn3orzXnV6DTQdOOW5UU+M5zKY3/BU47X420/oPw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.7.tgz", + "integrity": "sha512-fcVap4QwqmzQwQK9QU3keeEpCzTjnP9NJ171vI7GnD7nbkAIcP9biZhDUx88uRH9BabSsQDS0unUps88uZvFIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.6", + "@smithy/querystring-builder": "^4.2.6", + "@smithy/types": "^4.10.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.7.tgz", + "integrity": "sha512-CIbCTGGX5CI7tfewBPSYD9ycp2Vb2GW5xnXD1n7GcO9mu37EN7A6DvCHM9MX7pOeS1adMn5D+1yRwI3eABVbcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.6.tgz", + "integrity": "sha512-k3Dy9VNR37wfMh2/1RHkFf/e0rMyN0pjY0FdyY6ItJRjENYyVPRMwad6ZR1S9HFm6tTuIOd9pqKBmtJ4VHxvxg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.6.tgz", + "integrity": "sha512-+3T8LkH39YIhYHsv/Ec8lF+92nykZpU+XMBvAyXF/uLcTp86pxa5oSJk1vzaRY9N++qgDLYjzJ6OVbtAgDGwfw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.6.tgz", + "integrity": "sha512-E4t/V/q2T46RY21fpfznd1iSLTvCXKNKo4zJ1QuEFN4SE9gKfu2vb6bgq35LpufkQ+SETWIC7ZAf2GGvTlBaMQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.6.tgz", + "integrity": "sha512-ZXeh8UmH31JdcNsrQ1o9v1IVuva9JFwxIc6zTMxWX7wcmWvVR7Ai9aUEw5LraNKqdkAsb06clpM2sRH4Iy55Sg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.6.tgz", + "integrity": "sha512-0cjqjyfj+Gls30ntq45SsBtqF3dfJQCeqQPyGz58Pk8OgrAr5YiB7ZvDzjCA94p4r6DCI4qLm7FKobqBjf515w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.0.tgz", + "integrity": "sha512-M6qWfUNny6NFNy8amrCGIb9TfOMUkHVtg9bHtEFGRgfH7A7AtPpn/fcrToGPjVDK1ECuMVvqGQOXcZxmu9K+7A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.19.0", + "@smithy/middleware-serde": "^4.2.7", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "@smithy/url-parser": "^4.2.6", + "@smithy/util-middleware": "^4.2.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.16.tgz", + "integrity": "sha512-XPpNhNRzm3vhYm7YCsyw3AtmWggJbg1wNGAoqb7NBYr5XA5isMRv14jgbYyUV6IvbTBFZQdf2QpeW43LrRdStQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/service-error-classification": "^4.2.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-retry": "^4.2.6", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.7.tgz", + "integrity": "sha512-PFMVHVPgtFECeu4iZ+4SX6VOQT0+dIpm4jSPLLL6JLSkp9RohGqKBKD0cbiXdeIFS08Forp0UHI6kc0gIHenSA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.6.tgz", + "integrity": "sha512-JSbALU3G+JS4kyBZPqnJ3hxIYwOVRV7r9GNQMS6j5VsQDo5+Es5nddLfr9TQlxZLNHPvKSh+XSB0OuWGfSWFcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.6.tgz", + "integrity": "sha512-fYEyL59Qe82Ha1p97YQTMEQPJYmBS+ux76foqluaTVWoG9Px5J53w6NvXZNE3wP7lIicLDF7Vj1Em18XTX7fsA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.6", + "@smithy/shared-ini-file-loader": "^4.4.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.6.tgz", + "integrity": "sha512-Gsb9jf4ido5BhPfani4ggyrKDd3ZK+vTFWmUaZeFg5G3E5nhFmqiTzAIbHqmPs1sARuJawDiGMGR/nY+Gw6+aQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/querystring-builder": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.6.tgz", + "integrity": "sha512-a/tGSLPtaia2krbRdwR4xbZKO8lU67DjMk/jfY4QKt4PRlKML+2tL/gmAuhNdFDioO6wOq0sXkfnddNFH9mNUA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.6.tgz", + "integrity": "sha512-qLRZzP2+PqhE3OSwvY2jpBbP0WKTZ9opTsn+6IWYI0SKVpbG+imcfNxXPq9fj5XeaUTr7odpsNpK6dmoiM1gJQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.6.tgz", + "integrity": "sha512-MeM9fTAiD3HvoInK/aA8mgJaKQDvm8N0dKy6EiFaCfgpovQr4CaOkJC28XqlSRABM+sHdSQXbC8NZ0DShBMHqg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.6.tgz", + "integrity": "sha512-YmWxl32SQRw/kIRccSOxzS/Ib8/b5/f9ex0r5PR40jRJg8X1wgM3KrR2In+8zvOGVhRSXgvyQpw9yOSlmfmSnA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.6.tgz", + "integrity": "sha512-Q73XBrzJlGTut2nf5RglSntHKgAG0+KiTJdO5QQblLfr4TdliGwIAha1iZIjwisc3rA5ulzqwwsYC6xrclxVQg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.1.tgz", + "integrity": "sha512-tph+oQYPbpN6NamF030hx1gb5YN2Plog+GLaRHpoEDwp8+ZPG26rIJvStG9hkWzN2HBn3HcWg0sHeB0tmkYzqA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.6.tgz", + "integrity": "sha512-P1TXDHuQMadTMTOBv4oElZMURU4uyEhxhHfn+qOc2iofW9Rd4sZtBGx58Lzk112rIGVEYZT8eUMK4NftpewpRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.6", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.1.tgz", + "integrity": "sha512-1ovWdxzYprhq+mWqiGZlt3kF69LJthuQcfY9BIyHx9MywTFKzFapluku1QXoaBB43GCsLDxNqS+1v30ure69AA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.19.0", + "@smithy/middleware-endpoint": "^4.4.0", + "@smithy/middleware-stack": "^4.2.6", + "@smithy/protocol-http": "^5.3.6", + "@smithy/types": "^4.10.0", + "@smithy/util-stream": "^4.5.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.10.0.tgz", + "integrity": "sha512-K9mY7V/f3Ul+/Gz4LJANZ3vJ/yiBIwCyxe0sPT4vNJK63Srvd+Yk1IzP0t+nE7XFSpIGtzR71yljtnqpUTYFlQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.6.tgz", + "integrity": "sha512-tVoyzJ2vXp4R3/aeV4EQjBDmCuWxRa8eo3KybL7Xv4wEM16nObYh7H1sNfcuLWHAAAzb0RVyxUz1S3sGj4X+Tg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.15.tgz", + "integrity": "sha512-LiZQVAg/oO8kueX4c+oMls5njaD2cRLXRfcjlTYjhIqmwHnCwkQO5B3dMQH0c5PACILxGAQf6Mxsq7CjlDc76A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.18.tgz", + "integrity": "sha512-Kw2J+KzYm9C9Z9nY6+W0tEnoZOofstVCMTshli9jhQbQCy64rueGfKzPfuFBnVUqZD9JobxTh2DzHmPkp/Va/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.4", + "@smithy/credential-provider-imds": "^4.2.6", + "@smithy/node-config-provider": "^4.3.6", + "@smithy/property-provider": "^4.2.6", + "@smithy/smithy-client": "^4.10.1", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.6.tgz", + "integrity": "sha512-v60VNM2+mPvgHCBXEfMCYrQ0RepP6u6xvbAkMenfe4Mi872CqNkJzgcnQL837e8NdeDxBgrWQRTluKq5Lqdhfg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.6.tgz", + "integrity": "sha512-qrvXUkxBSAFomM3/OEMuDVwjh4wtqK8D2uDZPShzIqOylPst6gor2Cdp6+XrH4dyksAWq/bE2aSDYBTTnj0Rxg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.6.tgz", + "integrity": "sha512-x7CeDQLPQ9cb6xN7fRJEjlP9NyGW/YeXWc4j/RUhg4I+H60F0PEeRc2c/z3rm9zmsdiMFzpV/rT+4UHW6KM1SA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.7.tgz", + "integrity": "sha512-Uuy4S5Aj4oF6k1z+i2OtIBJUns4mlg29Ph4S+CqjR+f4XXpSFVgTCYLzMszHJTicYDBxKFtwq2/QSEDSS5l02A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.7", + "@smithy/node-http-handler": "^4.4.6", + "@smithy/types": "^4.10.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.6.tgz", + "integrity": "sha512-xU9HwUSik9UUCJmm530yvBy0AwlQFICveKmqvaaTukKkXEAhyiBdHtSrhPrH3rH+uz0ykyaE3LdgsX86C6mDCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.6", + "@smithy/types": "^4.10.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@spacebarchat/spacebar-webrtc-types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@spacebarchat/spacebar-webrtc-types/-/spacebar-webrtc-types-1.0.1.tgz", + "integrity": "sha512-WfBRUN2520w7o5vU9HNDug9alNvydQP7H/jwAy8LeHTHwlMMUw/60A54FQDIAtsahw787fR3QZ83UGjhKDzDTg==", + "dev": true, + "license": "AGPL-3.0-only" + }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", + "license": "MIT" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@toondepauw/node-zstd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd/-/node-zstd-1.2.0.tgz", + "integrity": "sha512-vVDvPUstF9gKEX+Kqp654bL+4UNus6pkmY8mlcJQpIw/Sn6sO9fl1PEwxB0d0unH5PmVfBUicf1lOXu74OLrCg==", + "license": "MIT", + "engines": { + "node": ">= 14" + }, + "optionalDependencies": { + "@toondepauw/node-zstd-darwin-arm64": "1.2.0", + "@toondepauw/node-zstd-darwin-x64": "1.2.0", + "@toondepauw/node-zstd-freebsd-x64": "1.2.0", + "@toondepauw/node-zstd-linux-arm64-gnu": "1.2.0", + "@toondepauw/node-zstd-linux-arm64-musl": "1.2.0", + "@toondepauw/node-zstd-linux-x64-gnu": "1.2.0", + "@toondepauw/node-zstd-linux-x64-musl": "1.2.0", + "@toondepauw/node-zstd-win32-x64-msvc": "1.2.0" + } + }, + "node_modules/@toondepauw/node-zstd-darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-darwin-arm64/-/node-zstd-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-fdBGlRSrf19M1+IJ98aJCLK6/HKQRnRlhE5i6EZ7kVfD2r6h5Bq9NqHAmQ5MgwtmfB9VMmiIrQAMIxKVeT54pg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 14" + } + }, + "node_modules/@toondepauw/node-zstd-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-darwin-x64/-/node-zstd-darwin-x64-1.2.0.tgz", + "integrity": "sha512-ZVu4zkQUgCQ8z1lLd7/LZzIYDKL48tPguIN8L4SRfLuWWlTq2/4OT6AmchztDbe5soLCDuw64t5X5otDvgsGJA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 14" + } + }, + "node_modules/@toondepauw/node-zstd-freebsd-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-freebsd-x64/-/node-zstd-freebsd-x64-1.2.0.tgz", + "integrity": "sha512-kDK+l3uP7htiWvM/klY32siEGIBk3K5Da4/DcXV2q/Vp+Ph86FWpQRzC46wJIpJB60xEU67pxFGLBH/8/RB02g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 14" + } + }, + "node_modules/@toondepauw/node-zstd-linux-arm64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-linux-arm64-gnu/-/node-zstd-linux-arm64-gnu-1.2.0.tgz", + "integrity": "sha512-KteUWam7McA0moNiRgu7KSgPu3FE+KXuh3ObqWoco5JK53Sp4Pgr/q1IYsdN6TYYbgJMkJpof62NIbyi6tnVfg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 14" + } + }, + "node_modules/@toondepauw/node-zstd-linux-arm64-musl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-linux-arm64-musl/-/node-zstd-linux-arm64-musl-1.2.0.tgz", + "integrity": "sha512-KjbGOXvPmorhWGD4kEVjdBz/gJgUOw1Eu0xziBwsq03URrI0XSDAY6YiJZpZtGnSjm7XonEpnBZcVwXH1HZz3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 14" + } + }, + "node_modules/@toondepauw/node-zstd-linux-x64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-linux-x64-gnu/-/node-zstd-linux-x64-gnu-1.2.0.tgz", + "integrity": "sha512-qNJDZGd/jplK/1J+8V87dYdrviAxNt1iwI/SQ0JR3HTz5Jdbu+BnVB9oMklCc/bGWa8zTkiZkl/EwA3gtbpo+Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 14" + } + }, + "node_modules/@toondepauw/node-zstd-linux-x64-musl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-linux-x64-musl/-/node-zstd-linux-x64-musl-1.2.0.tgz", + "integrity": "sha512-Cw1qnMXde8caitDMbk0vWrqnVHTSK+JB/Dp3XnZghQjBrD7JLj2qNzh0eTkxNFa+nijbo8HyyslWGswCugYZCw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 14" + } + }, + "node_modules/@toondepauw/node-zstd-win32-x64-msvc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@toondepauw/node-zstd-win32-x64-msvc/-/node-zstd-win32-x64-msvc-1.2.0.tgz", + "integrity": "sha512-7cD2/R7DLZHMxHTsQNtOX3vJCT4qd74a3nWjYYthJ89gqXwwEoC8BwttQxvHd0qCZ3S/cZ/BYoTikXMwMNmCEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 14" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "devOptional": 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==", + "devOptional": 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==", + "devOptional": 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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/amqplib": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.8.tgz", + "integrity": "sha512-vtDp8Pk1wsE/AuQ8/Rgtm6KUZYqcnTgNvEHwzCkX8rL7AGsC6zqAfKAAJhUZXFhM/Pp++tbnUHiam/8vVpPztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "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/i18next-node-fs-backend": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.5.tgz", + "integrity": "sha512-qLuS9SIlAnywXSzCIDi/TTm3DWzLKqLcd5O8K/OSX1Hif6soTu5H+GwZ9Jneoe0gRq6orwaPEe9d0dcmeAFLzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "i18next": ">=17.0.11" + } + }, + "node_modules/@types/json-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", + "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/module-alias": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/module-alias/-/module-alias-2.0.4.tgz", + "integrity": "sha512-5+G/QXO/DvHZw60FjvbDzO4JmlD/nG5m2/vVGt25VN1eeP3w2bCoks1Wa7VuptMPM1TxJdx6RjO70N9Fw0nZPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/morgan": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz", + "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/murmurhash-js": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/murmurhash-js/-/murmurhash-js-1.0.6.tgz", + "integrity": "sha512-P2RRwRpevEKO0FrK5xNjxBywg0Br24tEv8K2+iWg56PtcCUNGAkaaOWKBQiUpofA8H/gmgdHXrcvSgp2uXCVAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/needle": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@types/needle/-/needle-3.3.0.tgz", + "integrity": "sha512-UFIuc1gdyzAqeVUYpSL+cliw2MmU/ZUhVZKE7Zo4wPbgc8hbljeKSnn6ls6iG8r5jpegPXLUIhJ+Wb2kLVs8cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", + "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/nodemailer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.4.tgz", + "integrity": "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, + "node_modules/@types/notp": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/notp/-/notp-2.0.5.tgz", + "integrity": "sha512-ZsZS0PYUa6ZE4K3yOGerBvaxCp4ePf6ZmkFbPeilcqz2Ui/lmXox7KlRt7XZkXzqUgXhFLkc09ixyVmFLCU3gQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/probe-image-size": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/@types/probe-image-size/-/probe-image-size-7.2.5.tgz", + "integrity": "sha512-9Bg6d/GNnjmhMMxadDstwrSlquuuLf0jQuPszbU6n3QUfybif3V/ryD3J2i9iaiC5JB/FU/8E41n88SM/UB+Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/needle": "*", + "@types/node": "*" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sharp": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", + "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", + "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.50.0", + "@typescript-eslint/type-utils": "8.50.0", + "@typescript-eslint/utils": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0", + "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.0", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz", + "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0", + "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.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", + "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.50.0", + "@typescript-eslint/types": "^8.50.0", + "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.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", + "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0" + }, + "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.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", + "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", + "dev": true, + "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.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", + "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.0", + "@typescript-eslint/utils": "8.50.0", + "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.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", + "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", + "dev": true, + "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.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", + "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.50.0", + "@typescript-eslint/tsconfig-utils": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0", + "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==", + "dev": true, + "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==", + "dev": true, + "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.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz", + "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.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/visitor-keys": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", + "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.0", + "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/@typescript-eslint/visitor-keys/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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@yukikaze-bot/erlpack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@yukikaze-bot/erlpack/-/erlpack-1.0.1.tgz", + "integrity": "sha512-PCJ2lGCf8DsQtrE411PY+NTsolK48l4InNn1kcBo0iUllKZYGLqeqXEWGA/INrmwanKcoYkU4pBySqUFLQDEoA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.5", + "node-addon-api": "^4.0.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "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==", + "devOptional": true, + "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==", + "dev": true, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/amqplib": { + "version": "0.10.9", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.9.tgz", + "integrity": "sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==", + "license": "MIT", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT", + "optional": true + }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": 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==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "optional": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.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/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bcrypt/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "license": "MIT", + "optional": true + }, + "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/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "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/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cbor-extract": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", + "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", + "@cbor-extract/cbor-extract-linux-x64": "2.2.0", + "@cbor-extract/cbor-extract-win32-x64": "2.2.0" + } + }, + "node_modules/cbor-x": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.0.tgz", + "integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==", + "license": "MIT", + "optionalDependencies": { + "cbor-extract": "^2.2.0" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "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==", + "devOptional": 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/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "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/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "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/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/discord-protos": { + "version": "1.2.90", + "resolved": "https://registry.npmjs.org/discord-protos/-/discord-protos-1.2.90.tgz", + "integrity": "sha512-7W57dYO3ohAseS2k46UpGq+1b8FDuXIRz0WlxoWI6cgESuIPI6DCXfwc3tDeEaps81kfyOnBrT0LdNX+TrE/oQ==", + "license": "MIT", + "dependencies": { + "@protobuf-ts/runtime": "^2.9.6" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "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/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "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/email-providers": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/email-providers/-/email-providers-2.19.0.tgz", + "integrity": "sha512-HwDo7NLmBWXHtnXlediIbI1x+WSgTszWi35TZxlGhW49iW/dZkzC78f4RD/8osLY2Rqh62sgD2ufduKRO54Raw==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "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/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/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/encoding/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", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, + "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/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "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": "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "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/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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "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/espree/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==", + "dev": true, + "license": "Apache-2.0", + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exif-be-gone": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/exif-be-gone/-/exif-be-gone-1.5.1.tgz", + "integrity": "sha512-+fV9PoomNVR5Hmp0n1c0ZVl78/GaFrpnC0t7q4F9Aey8NcL+7Lutcez8KY2Ni30NWpvgLXawqiRFFwtdo4QgFg==", + "license": "ISC", + "bin": { + "exif-be-gone": "cli.js" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", + "optional": true + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.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/express/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/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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fast-zlib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-zlib/-/fast-zlib-2.0.1.tgz", + "integrity": "sha512-DCoYgNagM2Bt1VIpXpdGnRx4LzqJeYG0oh6Nf/7cWo6elTXkFGMw9CrRCYYUIapYNrozYMoyDRflx9mgT3Awyw==", + "license": "MIT", + "funding": { + "type": "patreon", + "url": "https://patreon.com/timotejroiko" + } + }, + "node_modules/fido2-lib": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/fido2-lib/-/fido2-lib-3.5.5.tgz", + "integrity": "sha512-XEXa+kRyQEq4Yse5bG+4rrvsxfOMp+ihzI2Uxi0/6OJ4ZaUdNKl8fBGDoyFZu/TKjCJGcL182/jf+UoyMSf9ow==", + "license": "MIT", + "dependencies": { + "@hexagon/base64": "~1.1.28", + "@peculiar/webcrypto": "~1.4.6", + "asn1js": "~3.0.6", + "cbor-x": "~1.6.0", + "jose": "^6.1.0", + "pkijs": "~3.3.2", + "punycode.js": "^2.3.1", + "tldts": "~7.0.17" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-type": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.1.tgz", + "integrity": "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "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": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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==", + "dev": true, + "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/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true, + "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/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "license": "MIT", + "optional": true, + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "devOptional": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "devOptional": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "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/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "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/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/i18next": { + "version": "25.7.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.3.tgz", + "integrity": "sha512-2XaT+HpYGuc2uTExq9TVRhLsso+Dxym6PWaKpn36wfBmTI779OQ7iP/XaZHzrnGyzU4SHpFrTYLKfVyBfAhVNA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-fs-backend": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.6.1.tgz", + "integrity": "sha512-eYWTX7QT7kJ0sZyCPK6x1q+R63zvNKv2D6UdbMf15A8vNb2ZLyw4NNNZxPFwXlIv/U+oUtg8SakW6ZgJZcoqHQ==", + "license": "MIT" + }, + "node_modules/i18next-http-middleware": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.9.0.tgz", + "integrity": "sha512-hM56al/hnQ8a6Us5uRvw/en1gSI3vePqBUa9nX4+ISvIHOU/li7ffu11FrI2aR83nphTe6ofo7mxhVu0PLAe2w==", + "license": "MIT" + }, + "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/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "license": "MIT", + "optional": true + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "devOptional": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "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-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.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/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "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/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jimp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", + "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/diff": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-gif": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-blur": "1.6.0", + "@jimp/plugin-circle": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-contain": "1.6.0", + "@jimp/plugin-cover": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-displace": "1.6.0", + "@jimp/plugin-dither": "1.6.0", + "@jimp/plugin-fisheye": "1.6.0", + "@jimp/plugin-flip": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/plugin-mask": "1.6.0", + "@jimp/plugin-print": "1.6.0", + "@jimp/plugin-quantize": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/plugin-rotate": "1.6.0", + "@jimp/plugin-threshold": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause", + "optional": true + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mailgun.js": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-12.4.0.tgz", + "integrity": "sha512-3Mwvwo02PUmFkW0zxSwvTjHBFCaMLb903RuQaEkTU8Z8medItb7Ey0K3XXLVDErArbEjPoRRqxPDdzWqXaokfQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "axios": "^1.12.1", + "base-64": "^1.0.0", + "url-join": "^4.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "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==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "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/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/module-alias": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", + "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", + "license": "MIT" + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/needle": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/node-2fa": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-2fa/-/node-2fa-2.0.3.tgz", + "integrity": "sha512-PQldrOhjuoZyoydMvMSctllPN1ZPZ1/NwkEcgYwY9faVqE/OymxR+3awPpbWZxm6acLKqvmNqQmdqTsqYyflFw==", + "license": "Apache-2.0", + "dependencies": { + "@types/notp": "^2.0.0", + "notp": "^2.0.3", + "thirty-two": "1.0.2", + "tslib": "^2.1.0" + } + }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-mailjet": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/node-mailjet/-/node-mailjet-6.0.11.tgz", + "integrity": "sha512-5UaawWhpJc1uFxUl9a07jE8a4ZOax5hj7XYC0LFYyWOP8Rp0I8XvjbiQ6gy/ns7KSw0hqmjytP85GQsdVGEx5g==", + "license": "MIT", + "optional": true, + "dependencies": { + "axios": "^1.12.0", + "json-bigint": "^1.0.0", + "url-join": "^4.0.0" + }, + "engines": { + "node": ">= 12.0.0", + "npm": ">= 6.9.0" + } + }, + "node_modules/nodemailer": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", + "license": "MIT-0", + "optional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/notp": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz", + "integrity": "sha512-oBig/2uqkjQ5AkBuw4QJYwkEWa/q+zHxI5/I5z6IeP2NT0alpJFsP/trrfCC+9xOAgQSZXssNi962kp5KBmypQ==", + "engines": { + "node": "> v0.6.0" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "license": "MIT", + "optional": true + }, + "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/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "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/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)", + "optional": true + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT", + "optional": true + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT", + "optional": true + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "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/path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "peer": true, + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-cursor": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/pg-cursor/-/pg-cursor-2.15.3.tgz", + "integrity": "sha512-eHw63TsiGtFEfAd7tOTZ+TLy+i/2ePKS20H84qCQ+aQ60pve05Okon9tKMC+YN3j6XyeFoHnaim7Lt9WVafQsA==", + "license": "MIT", + "peerDependencies": { + "pg": "^8" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-query-stream": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/pg-query-stream/-/pg-query-stream-4.10.3.tgz", + "integrity": "sha512-h2utrzpOIzeT9JfaqfvBbVuvCfBjH86jNfVrGGTbyepKAIOyTfDew0lAt8bbJjs9n/I5bGDl7S2sx6h5hPyJxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "pg-cursor": "^2.15.3" + }, + "peerDependencies": { + "pg": "^8" + } + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "license": "ISC", + "optional": true, + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/pkijs": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", + "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-quick": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-4.2.2.tgz", + "integrity": "sha512-uAh96tBW1SsD34VhhDmWuEmqbpfYc/B3j++5MC/6b3Cb8Ow7NJsvKFhg0eoGu2xXX+o9RkahkTK6sUdd8E7g5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.7", + "ignore": "^7.0.5", + "mri": "^1.2.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.2", + "tinyexec": "^0.3.2", + "tslib": "^2.8.1" + }, + "bin": { + "pretty-quick": "lib/cli.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://opencollective.com/pretty-quick" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/pretty-quick/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/pretty-quick/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/probe-image-size": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz", + "integrity": "sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==", + "license": "MIT", + "dependencies": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT", + "optional": true + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "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/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "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.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "license": "MIT", + "optional": true, + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "optional": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.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.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "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", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-xml-to-json": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz", + "integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.12.2" + } + }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sql-highlight": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", + "integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==", + "funding": [ + "https://github.com/scriptcoded/sql-highlight?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/scriptcoded" + } + ], + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/sqlite3/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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/stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", + "license": "MIT", + "dependencies": { + "debug": "2" + } + }, + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "engines": { + "node": ">=0.2.6" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT", + "optional": true + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "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/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tldts": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.19" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/token-types": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.1.0", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "peer": true, + "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/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "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/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typeorm": { + "version": "0.3.28", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", + "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", + "license": "MIT", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "ansis": "^4.2.0", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "dayjs": "^1.11.19", + "debug": "^4.4.3", + "dedent": "^1.7.0", + "dotenv": "^16.6.1", + "glob": "^10.5.0", + "reflect-metadata": "^0.2.2", + "sha.js": "^2.4.12", + "sql-highlight": "^6.1.0", + "tslib": "^2.8.1", + "uuid": "^11.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@sap/hana-client": "^2.14.22", + "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0 || ^6.0.0", + "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm/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/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/typeorm/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/typeorm/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/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/typeorm/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-json-schema": { + "version": "0.65.1", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.65.1.tgz", + "integrity": "sha512-tuGH7ff2jPaUYi6as3lHyHcKpSmXIqN7/mu50x3HlYn0EHzLpmt3nplZ7EuhUkO0eqDRc9GqWNkfjgBPIS9kxg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^18.11.9", + "glob": "^7.1.7", + "path-equal": "^1.2.5", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~5.5.0", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/typescript-json-schema/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/typescript-json-schema/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-json-schema/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "license": "MIT", + "optional": true + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "license": "MIT", + "optional": true, + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "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==", + "devOptional": 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/webcrypto-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", + "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.7.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/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/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "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/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/wretch": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/wretch/-/wretch-2.11.1.tgz", + "integrity": "sha512-rJejp30zE8Igs4Mvo4pxXaiOKdSIGYLxyVaY+jZTlAMfQyi/hZsDwMRa9R09N0iS9Q8cLvplKfHwwyBv6XRWsg==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "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/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT", + "optional": true + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "optional": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": 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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } } diff --git a/package.json b/package.json index 5482ae7..c08b79a 100644 --- a/package.json +++ b/package.json @@ -1,135 +1,135 @@ { - "name": "spacebar-server", - "version": "1.0.0", - "description": "A Spacebar server written in Node.js", - "scripts": { - "prepare": "husky install", - "postinstall": "patch-package", - "start": "node --enable-source-maps dist/bundle/start.js", - "start:api": "node --enable-source-maps dist/api/start.js", - "start:cdn": "node --enable-source-maps dist/cdn/start.js", - "start:gateway": "node --enable-source-maps dist/gateway/start.js", - "build": "npm run build:src && npm run generate:schema && npm run generate:openapi", - "build:src": "tsc -b -v", - "watch": "tsc -w -b .", - "test": "node scripts/test.js", - "lint": "eslint .", - "setup": "npm run build && npm run generate:schema", - "sync:db": "npm run build && node scripts/syncronise.js", - "generate:rights": "node scripts/rights.js", - "generate:schema": "node scripts/schema.js", - "generate:migration": "node -r dotenv/config -r module-alias/register node_modules/typeorm/cli.js migration:generate -d dist/util/util/Database.js", - "generate:openapi": "node scripts/openapi.js", - "add:license": "node scripts/license.js", - "migrate-from-staging": "node -r dotenv/config -r module-alias/register scripts/stagingMigration/index.js", - "node:tests": "npm run build:src && node -r dotenv/config -r module-alias/register --enable-source-maps --test --experimental-test-coverage dist/**/*.test.js" - }, - "main": "dist/bundle/index.js", - "types": "src/bundle/index.ts", - "repository": { - "type": "git", - "url": "git+https://github.com/spacebarchat/server.git" - }, - "author": "Spacebar", - "license": "AGPL-3.0-only", - "bugs": { - "url": "https://github.com/spacebarchat/server/issues" - }, - "imports": { - "#*": "./dist/*/index.js" - }, - "homepage": "https://spacebar.chat", - "devDependencies": { - "@eslint/eslintrc": "^3.3.3", - "@eslint/js": "^9.14.0", - "@spacebarchat/spacebar-webrtc-types": "^1.0.1", - "@types/amqplib": "^0.10.8", - "@types/bcrypt": "^6.0.0", - "@types/body-parser": "^1.19.6", - "@types/cookie-parser": "^1.4.10", - "@types/express": "^5.0.6", - "@types/i18next-node-fs-backend": "^2.1.5", - "@types/json-bigint": "^1.0.4", - "@types/jsonwebtoken": "^9.0.10", - "@types/module-alias": "^2.0.4", - "@types/morgan": "^1.9.10", - "@types/multer": "^2.0.0", - "@types/murmurhash-js": "^1.0.6", - "@types/node": "^24.10.4", - "@types/nodemailer": "^7.0.4", - "@types/probe-image-size": "^7.2.5", - "@types/sharp": "^0.31.1", - "@types/ws": "^8.18.1", - "@typescript-eslint/eslint-plugin": "^8.50.0", - "@typescript-eslint/parser": "^8.50.0", - "eslint": "^9.39.2", - "globals": "^16.5.0", - "husky": "^9.1.7", - "patch-package": "^8.0.1", - "prettier": "^3.7.4", - "pretty-quick": "^4.2.2", - "ts-node": "^10.9.2", - "typescript": "^5.9.3", - "typescript-json-schema": "^0.65.1" - }, - "dependencies": { - "@aws-sdk/client-s3": "^3.953.0", - "@toondepauw/node-zstd": "^1.2.0", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "amqplib": "^0.10.9", - "bcrypt": "^6.0.0", - "body-parser": "^2.2.1", - "chalk": "^5.6.2", - "cheerio": "^1.1.2", - "cookie-parser": "^1.4.7", - "discord-protos": "^1.2.90", - "dotenv": "^17.2.3", - "email-providers": "^2.19.0", - "exif-be-gone": "^1.5.1", - "express": "^5.2.1", - "fast-zlib": "^2.0.1", - "fido2-lib": "^3.5.5", - "file-type": "^21.1.1", - "form-data": "^4.0.5", - "i18next": "^25.7.3", - "i18next-fs-backend": "^2.6.1", - "i18next-http-middleware": "^3.9.0", - "image-size": "^2.0.2", - "json-bigint": "^1.0.0", - "jsonwebtoken": "^9.0.3", - "module-alias": "^2.2.3", - "morgan": "^1.10.1", - "ms": "^2.1.3", - "multer": "^2.0.2", - "murmurhash-js": "^1.0.0", - "node-2fa": "^2.0.3", - "pg": "^8.16.3", - "pg-query-stream": "^4.10.3", - "picocolors": "^1.1.1", - "probe-image-size": "^7.2.3", - "reflect-metadata": "^0.2.2", - "sqlite3": "^5.1.7", - "tslib": "^2.8.1", - "typeorm": "^0.3.28", - "wretch": "^2.11.1", - "ws": "^8.18.3" - }, - "_moduleAliases": { - "@spacebar/api": "dist/api", - "@spacebar/cdn": "dist/cdn", - "@spacebar/gateway": "dist/gateway", - "@spacebar/util": "dist/util", - "@spacebar/webrtc": "dist/webrtc", - "@spacebar/schemas": "dist/schemas", - "lambert-server": "dist/util/util/lambert-server" - }, - "optionalDependencies": { - "@sendgrid/mail": "^8.1.6", - "@yukikaze-bot/erlpack": "^1.0.1", - "jimp": "^1.6.0", - "mailgun.js": "^12.4.0", - "node-mailjet": "^6.0.11", - "nodemailer": "^7.0.11" - } + "name": "spacebar-server", + "version": "1.0.0", + "description": "A Spacebar server written in Node.js", + "scripts": { + "prepare": "husky install", + "postinstall": "patch-package", + "start": "node --enable-source-maps dist/bundle/start.js", + "start:api": "node --enable-source-maps dist/api/start.js", + "start:cdn": "node --enable-source-maps dist/cdn/start.js", + "start:gateway": "node --enable-source-maps dist/gateway/start.js", + "build": "npm run build:src && npm run generate:schema && npm run generate:openapi", + "build:src": "tsc -b -v", + "watch": "tsc -w -b .", + "test": "node scripts/test.js", + "lint": "eslint .", + "setup": "npm run build && npm run generate:schema", + "sync:db": "npm run build && node scripts/syncronise.js", + "generate:rights": "node scripts/rights.js", + "generate:schema": "node scripts/schema.js", + "generate:migration": "node -r dotenv/config -r module-alias/register node_modules/typeorm/cli.js migration:generate -d dist/util/util/Database.js", + "generate:openapi": "node scripts/openapi.js", + "add:license": "node scripts/license.js", + "migrate-from-staging": "node -r dotenv/config -r module-alias/register scripts/stagingMigration/index.js", + "node:tests": "npm run build:src && node -r dotenv/config -r module-alias/register --enable-source-maps --test --experimental-test-coverage dist/**/*.test.js" + }, + "main": "dist/bundle/index.js", + "types": "src/bundle/index.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/spacebarchat/server.git" + }, + "author": "Spacebar", + "license": "AGPL-3.0-only", + "bugs": { + "url": "https://github.com/spacebarchat/server/issues" + }, + "imports": { + "#*": "./dist/*/index.js" + }, + "homepage": "https://spacebar.chat", + "devDependencies": { + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^9.14.0", + "@spacebarchat/spacebar-webrtc-types": "^1.0.1", + "@types/amqplib": "^0.10.8", + "@types/bcrypt": "^6.0.0", + "@types/body-parser": "^1.19.6", + "@types/cookie-parser": "^1.4.10", + "@types/express": "^5.0.6", + "@types/i18next-node-fs-backend": "^2.1.5", + "@types/json-bigint": "^1.0.4", + "@types/jsonwebtoken": "^9.0.10", + "@types/module-alias": "^2.0.4", + "@types/morgan": "^1.9.10", + "@types/multer": "^2.0.0", + "@types/murmurhash-js": "^1.0.6", + "@types/node": "^24.10.4", + "@types/nodemailer": "^7.0.4", + "@types/probe-image-size": "^7.2.5", + "@types/sharp": "^0.31.1", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^8.50.0", + "@typescript-eslint/parser": "^8.50.0", + "eslint": "^9.39.2", + "globals": "^16.5.0", + "husky": "^9.1.7", + "patch-package": "^8.0.1", + "prettier": "^3.7.4", + "pretty-quick": "^4.2.2", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-json-schema": "^0.65.1" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.953.0", + "@toondepauw/node-zstd": "^1.2.0", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "amqplib": "^0.10.9", + "bcrypt": "^6.0.0", + "body-parser": "^2.2.1", + "chalk": "^5.6.2", + "cheerio": "^1.1.2", + "cookie-parser": "^1.4.7", + "discord-protos": "^1.2.90", + "dotenv": "^17.2.3", + "email-providers": "^2.19.0", + "exif-be-gone": "^1.5.1", + "express": "^5.2.1", + "fast-zlib": "^2.0.1", + "fido2-lib": "^3.5.5", + "file-type": "^21.1.1", + "form-data": "^4.0.5", + "i18next": "^25.7.3", + "i18next-fs-backend": "^2.6.1", + "i18next-http-middleware": "^3.9.0", + "image-size": "^2.0.2", + "json-bigint": "^1.0.0", + "jsonwebtoken": "^9.0.3", + "module-alias": "^2.2.3", + "morgan": "^1.10.1", + "ms": "^2.1.3", + "multer": "^2.0.2", + "murmurhash-js": "^1.0.0", + "node-2fa": "^2.0.3", + "pg": "^8.16.3", + "pg-query-stream": "^4.10.3", + "picocolors": "^1.1.1", + "probe-image-size": "^7.2.3", + "reflect-metadata": "^0.2.2", + "sqlite3": "^5.1.7", + "tslib": "^2.8.1", + "typeorm": "^0.3.28", + "wretch": "^2.11.1", + "ws": "^8.18.3" + }, + "_moduleAliases": { + "@spacebar/api": "dist/api", + "@spacebar/cdn": "dist/cdn", + "@spacebar/gateway": "dist/gateway", + "@spacebar/util": "dist/util", + "@spacebar/webrtc": "dist/webrtc", + "@spacebar/schemas": "dist/schemas", + "lambert-server": "dist/util/util/lambert-server" + }, + "optionalDependencies": { + "@sendgrid/mail": "^8.1.6", + "@yukikaze-bot/erlpack": "^1.0.1", + "jimp": "^1.6.0", + "mailgun.js": "^12.4.0", + "node-mailjet": "^6.0.11", + "nodemailer": "^7.0.11" + } } diff --git a/scripts/genIndex.js b/scripts/genIndex.js index c1b6822..e4601f3 100644 --- a/scripts/genIndex.js +++ b/scripts/genIndex.js @@ -41,29 +41,29 @@ // node scripts/genIndex.js /path/to/dir const targetDir = process.argv[2]; if (!targetDir) { - console.error("Please provide a target directory."); - process.exit(1); + console.error("Please provide a target directory."); + process.exit(1); } if (fs.existsSync(path.join(targetDir, "index.js")) || fs.existsSync(path.join(targetDir, "index.ts"))) { - console.error("index.js or index.ts already exists in the target directory."); - process.exit(1); + console.error("index.js or index.ts already exists in the target directory."); + process.exit(1); } const dirs = fs.readdirSync(targetDir).filter((f) => fs.statSync(path.join(targetDir, f)).isDirectory()); for (const dir of dirs) { - content += `export * from "./${dir}";\n`; + content += `export * from "./${dir}";\n`; } const files = fs.readdirSync(targetDir).filter((f) => f.endsWith(".js") || f.endsWith(".ts")); for (const file of files) { - const filePath = path.join(targetDir, file); - const stat = fs.statSync(filePath); - if (stat.isFile()) { - const ext = path.extname(file); - const base = path.basename(file, ext); - content += `export * from "./${base}";\n`; - } + const filePath = path.join(targetDir, file); + const stat = fs.statSync(filePath); + if (stat.isFile()) { + const ext = path.extname(file); + const base = path.basename(file, ext); + content += `export * from "./${base}";\n`; + } } fs.writeFileSync(path.join(targetDir, "index.ts"), content); diff --git a/scripts/license.js b/scripts/license.js index 380a3ed..0cc9b09 100644 --- a/scripts/license.js +++ b/scripts/license.js @@ -12,43 +12,43 @@ const SPACEBAR_SOURCE_DIR = Path.join(__dirname, "..", "src"); const SPACEBAR_SCRIPTS_DIR = Path.join(__dirname); const SPACEBAR_LICENSE_PREAMBLE = fs - .readFileSync(Path.join(__dirname, "util", "licensePreamble.txt")) - .toString() - .split("\r") // remove windows bs - .join("") // ^ - .split("\n") - .map((x) => `\t${x}`) - .join("\n"); + .readFileSync(Path.join(__dirname, "util", "licensePreamble.txt")) + .toString() + .split("\r") // remove windows bs + .join("") // ^ + .split("\n") + .map((x) => `\t${x}`) + .join("\n"); const languageCommentStrings = { - js: ["/*", "*/"], - ts: ["/*", "*/"], + js: ["/*", "*/"], + ts: ["/*", "*/"], }; const addToDir = (dir) => { - const files = walk(dir, Object.keys(languageCommentStrings)); + const files = walk(dir, Object.keys(languageCommentStrings)); - for (let path of files) { - const file = fs.readFileSync(path).toString().split("\r").join(""); - const fileType = path.slice(path.lastIndexOf(".") + 1); - const commentStrings = languageCommentStrings[fileType]; - if (!commentStrings) continue; + for (let path of files) { + const file = fs.readFileSync(path).toString().split("\r").join(""); + const fileType = path.slice(path.lastIndexOf(".") + 1); + const commentStrings = languageCommentStrings[fileType]; + if (!commentStrings) continue; - const preamble = commentStrings[0] + "\n" + SPACEBAR_LICENSE_PREAMBLE + "\n" + commentStrings[1]; + const preamble = commentStrings[0] + "\n" + SPACEBAR_LICENSE_PREAMBLE + "\n" + commentStrings[1]; - if (file.startsWith(preamble)) { - continue; - } + if (file.startsWith(preamble)) { + continue; + } - // This is kind of lame. - if (file.includes("@fc-license-skip") && path != __filename) { - console.log(`skipping ${path} as it has a different license.`); - continue; - } + // This is kind of lame. + if (file.includes("@fc-license-skip") && path != __filename) { + console.log(`skipping ${path} as it has a different license.`); + continue; + } - console.log(`writing to ${path}`); - fs.writeFileSync(path, preamble + "\n\n" + file); - } + console.log(`writing to ${path}`); + fs.writeFileSync(path, preamble + "\n\n" + file); + } }; addToDir(SPACEBAR_SOURCE_DIR); diff --git a/scripts/openapi.js b/scripts/openapi.js index 39396d8..5afee15 100644 --- a/scripts/openapi.js +++ b/scripts/openapi.js @@ -32,227 +32,227 @@ const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); let specification = { - openapi: "3.1.0", - info: { - title: "Spacebar Server", - description: - "Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.", - license: { - name: "AGPLV3", - url: "https://www.gnu.org/licenses/agpl-3.0.en.html", - }, - version: "1.0.0", - }, - externalDocs: { - description: "Spacebar Docs", - url: "https://docs.spacebar.chat", - }, - servers: [ - { - url: "https://old.server.spacebar.chat/api/", - description: "Official Spacebar Instance", - }, - ], - components: { - securitySchemes: { - bearer: { - type: "http", - scheme: "bearer", - description: "Bearer/Bot prefixes are not required.", - bearerFormat: "JWT", - in: "header", - }, - }, - }, - tags: [], - paths: {}, + openapi: "3.1.0", + info: { + title: "Spacebar Server", + description: + "Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.", + license: { + name: "AGPLV3", + url: "https://www.gnu.org/licenses/agpl-3.0.en.html", + }, + version: "1.0.0", + }, + externalDocs: { + description: "Spacebar Docs", + url: "https://docs.spacebar.chat", + }, + servers: [ + { + url: "https://old.server.spacebar.chat/api/", + description: "Official Spacebar Instance", + }, + ], + components: { + securitySchemes: { + bearer: { + type: "http", + scheme: "bearer", + description: "Bearer/Bot prefixes are not required.", + bearerFormat: "JWT", + in: "header", + }, + }, + }, + tags: [], + paths: {}, }; const schemaRegEx = new RegExp(/^[\w.]+$/); function combineSchemas(schemas) { - let definitions = {}; + let definitions = {}; - for (const name in schemas) { - definitions = { - ...definitions, - ...schemas[name].definitions, - [name]: { - ...schemas[name], - definitions: undefined, - $schema: undefined, - }, - }; - } + for (const name in schemas) { + definitions = { + ...definitions, + ...schemas[name].definitions, + [name]: { + ...schemas[name], + definitions: undefined, + $schema: undefined, + }, + }; + } - for (const key in definitions) { - if (!schemaRegEx.test(key)) { - console.error(` \x1b[5m${bgRedBright("ERROR")}\x1b[25m Invalid schema name: ${key}, context:`, definitions[key]); - continue; - } - specification.components = specification.components || {}; - specification.components.schemas = specification.components.schemas || {}; - specification.components.schemas[key] = definitions[key]; - delete definitions[key].additionalProperties; - delete definitions[key].$schema; - const definition = definitions[key]; + for (const key in definitions) { + if (!schemaRegEx.test(key)) { + console.error(` \x1b[5m${bgRedBright("ERROR")}\x1b[25m Invalid schema name: ${key}, context:`, definitions[key]); + continue; + } + specification.components = specification.components || {}; + specification.components.schemas = specification.components.schemas || {}; + specification.components.schemas[key] = definitions[key]; + delete definitions[key].additionalProperties; + delete definitions[key].$schema; + const definition = definitions[key]; - if (typeof definition.properties === "object") { - for (const property of Object.values(definition.properties)) { - if (Array.isArray(property.type)) { - if (property.type.includes("null")) { - property.type = property.type.find((x) => x !== "null"); - property.nullable = true; - } - } - } - } - } + if (typeof definition.properties === "object") { + for (const property of Object.values(definition.properties)) { + if (Array.isArray(property.type)) { + if (property.type.includes("null")) { + property.type = property.type.find((x) => x !== "null"); + property.nullable = true; + } + } + } + } + } - return definitions; + return definitions; } function getTag(key) { - return key.match(/\/([\w-]+)/)[1]; + return key.match(/\/([\w-]+)/)[1]; } function apiRoutes(missingRoutes) { - const routes = getRouteDescriptions(); + const routes = getRouteDescriptions(); - // populate tags - const tags = Array.from(routes.keys()) - .map((x) => getTag(x)) - .sort((a, b) => a.localeCompare(b)); - specification.tags = [...new Set(tags)].map((x) => ({ name: x })); + // populate tags + const tags = Array.from(routes.keys()) + .map((x) => getTag(x)) + .sort((a, b) => a.localeCompare(b)); + specification.tags = [...new Set(tags)].map((x) => ({ name: x })); - routes.forEach((route, pathAndMethod) => { - const [p, method] = pathAndMethod.split("|"); - const path = p.replace(/:(\w+)/g, "{$1}"); + routes.forEach((route, pathAndMethod) => { + const [p, method] = pathAndMethod.split("|"); + const path = p.replace(/:(\w+)/g, "{$1}"); - let obj = specification.paths[path]?.[method] || {}; - obj["x-right-required"] = route.right; - obj["x-permission-required"] = route.permission; - obj["x-fires-event"] = route.event; + let obj = specification.paths[path]?.[method] || {}; + obj["x-right-required"] = route.right; + obj["x-permission-required"] = route.permission; + obj["x-fires-event"] = route.event; - if ( - !NO_AUTHORIZATION_ROUTES.some((x) => { - if (typeof x === "string") return (method.toUpperCase() + " " + path).startsWith(x); - return x.test(method.toUpperCase() + " " + path); - }) - ) { - obj.security = [{ bearer: [] }]; - } + if ( + !NO_AUTHORIZATION_ROUTES.some((x) => { + if (typeof x === "string") return (method.toUpperCase() + " " + path).startsWith(x); + return x.test(method.toUpperCase() + " " + path); + }) + ) { + obj.security = [{ bearer: [] }]; + } - if (route.description) obj.description = route.description; - if (route.summary) obj.summary = route.summary; - if (route.deprecated) obj.deprecated = route.deprecated; + if (route.description) obj.description = route.description; + if (route.summary) obj.summary = route.summary; + if (route.deprecated) obj.deprecated = route.deprecated; - if (route.requestBody) { - obj.requestBody = { - required: true, - content: { - "application/json": { - schema: { - $ref: `#/components/schemas/${route.requestBody}`, - }, - }, - }, - }; - } + if (route.requestBody) { + obj.requestBody = { + required: true, + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${route.requestBody}`, + }, + }, + }, + }; + } - if (route.responses) { - obj.responses = {}; + if (route.responses) { + obj.responses = {}; - for (const [k, v] of Object.entries(route.responses)) { - if (v.body) - obj.responses[k] = { - description: obj?.responses?.[k]?.description || "", - content: { - "application/json": { - schema: { - $ref: `#/components/schemas/${v.body}`, - }, - }, - }, - }; - else - obj.responses[k] = { - description: obj?.responses?.[k]?.description || "No description available", - }; - } - } else { - obj.responses = { - default: { - description: "No description available", - }, - }; - } + for (const [k, v] of Object.entries(route.responses)) { + if (v.body) + obj.responses[k] = { + description: obj?.responses?.[k]?.description || "", + content: { + "application/json": { + schema: { + $ref: `#/components/schemas/${v.body}`, + }, + }, + }, + }; + else + obj.responses[k] = { + description: obj?.responses?.[k]?.description || "No description available", + }; + } + } else { + obj.responses = { + default: { + description: "No description available", + }, + }; + } - // handles path parameters - if (p.includes(":")) { - obj.parameters = p.match(/:\w+/g)?.map((x) => ({ - name: x.replace(":", ""), - in: "path", - required: true, - schema: { type: "string" }, - description: x.replace(":", ""), - })); - } + // handles path parameters + if (p.includes(":")) { + obj.parameters = p.match(/:\w+/g)?.map((x) => ({ + name: x.replace(":", ""), + in: "path", + required: true, + schema: { type: "string" }, + description: x.replace(":", ""), + })); + } - if (route.query) { - // map to array - const query = Object.entries(route.query).map(([k, v]) => ({ - name: k, - in: "query", - required: v.required, - schema: { type: v.type }, - description: v.description, - })); + if (route.query) { + // map to array + const query = Object.entries(route.query).map(([k, v]) => ({ + name: k, + in: "query", + required: v.required, + schema: { type: v.type }, + description: v.description, + })); - obj.parameters = [...(obj.parameters || []), ...query]; - } + obj.parameters = [...(obj.parameters || []), ...query]; + } - obj.tags = [...new Set([...(obj.tags || []), getTag(p)])]; + obj.tags = [...new Set([...(obj.tags || []), getTag(p)])]; - if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) { - obj["x-badges"] = [ - { - label: "Spacebar-only", - color: "red", - }, - ]; - } + if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) { + obj["x-badges"] = [ + { + label: "Spacebar-only", + color: "red", + }, + ]; + } - specification.paths[path] = Object.assign(specification.paths[path] || {}, { - [method]: obj, - }); - }); + specification.paths[path] = Object.assign(specification.paths[path] || {}, { + [method]: obj, + }); + }); } async function main() { - console.log("Generating OpenAPI Specification..."); + console.log("Generating OpenAPI Specification..."); - const routesRes = await fetch("https://github.com/spacebarchat/missing-routes/raw/main/missing.json", { - headers: { - Accept: "application/json", - }, - }); - const missingRoutes = await routesRes.json(); + const routesRes = await fetch("https://github.com/spacebarchat/missing-routes/raw/main/missing.json", { + headers: { + Accept: "application/json", + }, + }); + const missingRoutes = await routesRes.json(); - combineSchemas(schemas); - apiRoutes(missingRoutes); + combineSchemas(schemas); + apiRoutes(missingRoutes); - fs.writeFileSync(openapiPath, JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")); - console.log("Wrote OpenAPI specification to", openapiPath); - const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds); - console.log( - "Specification contains", - Object.keys(specification.paths).length, - "paths and", - Object.keys(specification.components.schemas).length, - "schemas in", - elapsedMs, - "ms.", - ); + fs.writeFileSync(openapiPath, JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")); + console.log("Wrote OpenAPI specification to", openapiPath); + const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds); + console.log( + "Specification contains", + Object.keys(specification.paths).length, + "paths and", + Object.keys(specification.components.schemas).length, + "schemas in", + elapsedMs, + "ms.", + ); } main(); diff --git a/scripts/schema.js b/scripts/schema.js index 1779c8d..67c5563 100644 --- a/scripts/schema.js +++ b/scripts/schema.js @@ -24,10 +24,10 @@ const conWarn = console.warn; console.warn = (...args) => { - // silence some expected warnings - if (args[0] === "initializer is expression for property id") return; - if (args[0].startsWith("unknown initializer for property ") && args[0].endsWith("[object Object]")) return; - conWarn(...args); + // silence some expected warnings + if (args[0] === "initializer is expression for property id") return; + if (args[0].startsWith("unknown initializer for property ") && args[0].endsWith("[object Object]")) return; + conWarn(...args); }; const path = require("path"); @@ -41,27 +41,27 @@ // @type {TJS.PartialArgs} const settings = { - required: true, - ignoreErrors: true, - excludePrivate: true, - defaultNumberType: "integer", - noExtraProps: true, - defaultProps: false, - useTypeOfKeyword: true, // should help catch functions? + required: true, + ignoreErrors: true, + excludePrivate: true, + defaultNumberType: "integer", + noExtraProps: true, + defaultProps: false, + useTypeOfKeyword: true, // should help catch functions? }; const baseClassProperties = [ - // BaseClass methods - "toJSON", - "hasId", - "save", - "remove", - "softRemove", - "recover", - "reload", - "assign", - "_do_validate", // ? - "hasId", // ? + // BaseClass methods + "toJSON", + "hasId", + "save", + "remove", + "softRemove", + "recover", + "reload", + "assign", + "_do_validate", // ? + "hasId", // ? ]; const ExcludeAndWarn = [...exclusionList.manualWarn, ...exclusionList.manualWarnRe.map((r) => new RegExp(r))]; @@ -69,315 +69,315 @@ const Included = [...exclusionList.include, ...exclusionList.includeRe.map((r) => new RegExp(r))]; const excludedLambdas = [ - (n, s) => { - // attempt to import - if (JSON.stringify(s).includes(`#/definitions/import(`)) { - console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it attempted to use import().`); - exclusionList.auto.push({ value: n, reason: "Uses import()" }); - return true; - } - }, - (n, s) => { - if (JSON.stringify(s).includes(process.cwd())) { - console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it leaked $PWD.`); - exclusionList.auto.push({ value: n, reason: "Leaked $PWD" }); - return true; - } - }, - (n, s) => { - if (JSON.stringify(s).includes(process.env.HOME)) { - console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it leaked a $HOME path.`); - exclusionList.auto.push({ value: n, reason: "Leaked $HOME" }); - return true; - } - }, - (n, s) => { - if (s["$ref"] === `#/definitions/${n}`) { - console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it is a self-reference only schema.`); - exclusionList.auto.push({ value: n, reason: "Self-reference only schema" }); - // fs.writeFileSync(`fucked/${n}.json`, JSON.stringify(s, null, 4)); - return true; - } - }, - (n, s) => { - if (s.description?.match(/Smithy/)) { - console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it appears to be an AWS Smithy schema.`); - exclusionList.auto.push({ value: n, reason: "AWS Smithy schema" }); - return true; - } - }, - (n, s) => { - if (s.description?.startsWith("

")) { - console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as we don't use HTML paragraphs for descriptions.`); - exclusionList.auto.push({ value: n, reason: "HTML paragraph in description" }); - return true; - } - }, - (n, s) => { - if (s.properties && Object.keys(s.properties).every((x) => x[0] === x[0].toUpperCase())) { - console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as all its properties have uppercase characters.`); - exclusionList.auto.push({ value: n, reason: "Schema with only uppercase properties" }); - return true; - } - }, - // (n, s) => { - // if (JSON.stringify(s).length <= 300) { - // console.log({n, s}); - // } - // } + (n, s) => { + // attempt to import + if (JSON.stringify(s).includes(`#/definitions/import(`)) { + console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it attempted to use import().`); + exclusionList.auto.push({ value: n, reason: "Uses import()" }); + return true; + } + }, + (n, s) => { + if (JSON.stringify(s).includes(process.cwd())) { + console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it leaked $PWD.`); + exclusionList.auto.push({ value: n, reason: "Leaked $PWD" }); + return true; + } + }, + (n, s) => { + if (JSON.stringify(s).includes(process.env.HOME)) { + console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it leaked a $HOME path.`); + exclusionList.auto.push({ value: n, reason: "Leaked $HOME" }); + return true; + } + }, + (n, s) => { + if (s["$ref"] === `#/definitions/${n}`) { + console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it is a self-reference only schema.`); + exclusionList.auto.push({ value: n, reason: "Self-reference only schema" }); + // fs.writeFileSync(`fucked/${n}.json`, JSON.stringify(s, null, 4)); + return true; + } + }, + (n, s) => { + if (s.description?.match(/Smithy/)) { + console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as it appears to be an AWS Smithy schema.`); + exclusionList.auto.push({ value: n, reason: "AWS Smithy schema" }); + return true; + } + }, + (n, s) => { + if (s.description?.startsWith("

")) { + console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as we don't use HTML paragraphs for descriptions.`); + exclusionList.auto.push({ value: n, reason: "HTML paragraph in description" }); + return true; + } + }, + (n, s) => { + if (s.properties && Object.keys(s.properties).every((x) => x[0] === x[0].toUpperCase())) { + console.log(`\r${redBright("[WARN]")} Omitting schema ${n} as all its properties have uppercase characters.`); + exclusionList.auto.push({ value: n, reason: "Schema with only uppercase properties" }); + return true; + } + }, + // (n, s) => { + // if (JSON.stringify(s).length <= 300) { + // console.log({n, s}); + // } + // } ]; function includesMatch(haystack, needles, log = false) { - for (const needle of needles) { - const match = needle instanceof RegExp ? needle.test(haystack) : haystack === needle; - if (match) { - if (log) console.warn(redBright("[WARN]:"), "Excluding schema", haystack, "due to match with", needle); - return needle; - } - } - return null; + for (const needle of needles) { + const match = needle instanceof RegExp ? needle.test(haystack) : haystack === needle; + if (match) { + if (log) console.warn(redBright("[WARN]:"), "Excluding schema", haystack, "due to match with", needle); + return needle; + } + } + return null; } async function main() { - const stepSw = Stopwatch.startNew(); + const stepSw = Stopwatch.startNew(); - process.stdout.write("Loading program... "); - const program = TJS.programFromConfig(path.join(__dirname, "..", "tsconfig.json"), walk(path.join(__dirname, "..", "src", "schemas"))); - const generator = TJS.buildGenerator(program, settings); - if (!generator || !program) { - console.log(redBright("Failed to create schema generator.")); - return; - } + process.stdout.write("Loading program... "); + const program = TJS.programFromConfig(path.join(__dirname, "..", "tsconfig.json"), walk(path.join(__dirname, "..", "src", "schemas"))); + const generator = TJS.buildGenerator(program, settings); + if (!generator || !program) { + console.log(redBright("Failed to create schema generator.")); + return; + } - const elapsedLoad = stepSw.getElapsedAndReset(); - process.stdout.write("Done in " + yellowBright(elapsedLoad.totalMilliseconds + "." + elapsedLoad.microseconds) + " ms\n"); + const elapsedLoad = stepSw.getElapsedAndReset(); + process.stdout.write("Done in " + yellowBright(elapsedLoad.totalMilliseconds + "." + elapsedLoad.microseconds) + " ms\n"); - process.stdout.write("Generating schema list... "); - let schemas = generator.getUserSymbols().filter((x) => { - return ( - (x.endsWith("Schema") || x.endsWith("Response") || x.startsWith("API")) && - // !ExcludeAndWarn.some((exc) => { - // const match = exc instanceof RegExp ? exc.test(x) : x === exc; - // if (match) console.warn("Warning: Excluding schema", x); - // return match; - // }) && - // !Excluded.some((exc) => (exc instanceof RegExp ? exc.test(x) : x === exc)) - (includesMatch(x, Included) || (!includesMatch(x, ExcludeAndWarn, true) && !includesMatch(x, Excluded))) - ); - }); - //.sort((a,b) => a.localeCompare(b)); + process.stdout.write("Generating schema list... "); + let schemas = generator.getUserSymbols().filter((x) => { + return ( + (x.endsWith("Schema") || x.endsWith("Response") || x.startsWith("API")) && + // !ExcludeAndWarn.some((exc) => { + // const match = exc instanceof RegExp ? exc.test(x) : x === exc; + // if (match) console.warn("Warning: Excluding schema", x); + // return match; + // }) && + // !Excluded.some((exc) => (exc instanceof RegExp ? exc.test(x) : x === exc)) + (includesMatch(x, Included) || (!includesMatch(x, ExcludeAndWarn, true) && !includesMatch(x, Excluded))) + ); + }); + //.sort((a,b) => a.localeCompare(b)); - const elapsedList = stepSw.getElapsedAndReset(); - process.stdout.write("Done in " + yellowBright(elapsedList.totalMilliseconds + "." + elapsedList.microseconds) + " ms\n"); - console.log("Found", yellowBright(schemas.length), "schemas to process."); + const elapsedList = stepSw.getElapsedAndReset(); + process.stdout.write("Done in " + yellowBright(elapsedList.totalMilliseconds + "." + elapsedList.microseconds) + " ms\n"); + console.log("Found", yellowBright(schemas.length), "schemas to process."); - let definitions = {}; - let nestedDefinitions = {}; - let writePromises = []; + let definitions = {}; + let nestedDefinitions = {}; + let writePromises = []; - if (process.env.WRITE_SCHEMA_DIR === "true") { - fs.rmSync("schemas_orig", { recursive: true, force: true }); - fs.mkdirSync("schemas_orig"); + if (process.env.WRITE_SCHEMA_DIR === "true") { + fs.rmSync("schemas_orig", { recursive: true, force: true }); + fs.mkdirSync("schemas_orig"); - fs.rmSync("schemas_nested", { recursive: true, force: true }); - fs.mkdirSync("schemas_nested"); + fs.rmSync("schemas_nested", { recursive: true, force: true }); + fs.mkdirSync("schemas_nested"); - fs.rmSync("schemas_final", { recursive: true, force: true }); - fs.mkdirSync("schemas_final"); - } + fs.rmSync("schemas_final", { recursive: true, force: true }); + fs.mkdirSync("schemas_final"); + } - const schemaSw = Stopwatch.startNew(); - for (const name of schemas) { - process.stdout.write(`Processing schema ${name}... `); - const part = TJS.generateSchema(program, name, settings, [], generator); - if (!part) continue; + const schemaSw = Stopwatch.startNew(); + for (const name of schemas) { + process.stdout.write(`Processing schema ${name}... `); + const part = TJS.generateSchema(program, name, settings, [], generator); + if (!part) continue; - if (definitions[name]) { - process.stdout.write(yellow(` [ERROR] Duplicate schema name detected: ${name}. Overwriting previous schema.`)); - } + if (definitions[name]) { + process.stdout.write(yellow(` [ERROR] Duplicate schema name detected: ${name}. Overwriting previous schema.`)); + } - if (!includesMatch(name, Included) && excludedLambdas.some((fn) => fn(name, part))) { - continue; - } + if (!includesMatch(name, Included) && excludedLambdas.some((fn) => fn(name, part))) { + continue; + } - if (process.env.WRITE_SCHEMA_DIR === "true") writePromises.push(async () => await fsp.writeFile(path.join("schemas_orig", `${name}.json`), JSON.stringify(part, null, 4))); + if (process.env.WRITE_SCHEMA_DIR === "true") writePromises.push(async () => await fsp.writeFile(path.join("schemas_orig", `${name}.json`), JSON.stringify(part, null, 4))); - // testing: - function mergeDefs(schemaName, schema) { - if (schema.definitions) { - // schema["x-sb-defs"] = Object.keys(schema.definitions); - process.stdout.write(cyanBright("Processing nested... ")); - for (const defKey in schema.definitions) { - if (definitions[defKey] && deepEqual(definitions[defKey], schema.definitions[defKey])) { - // console.log("Definition", defKey, "from schema", schemaName, "is identical to existing definition, skipping."); - schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey)); - process.stdout.write(greenBright("T")); - } else if (!nestedDefinitions[defKey]) { - nestedDefinitions[defKey] = schema.definitions[defKey]; - schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey)); - // console.log("Tracking sub-definition", defKey, "from schema", schemaName); - process.stdout.write(green("N")); - } else if (!deepEqual(nestedDefinitions[defKey], schema.definitions[defKey])) { - console.log(redBright("[ERROR]"), "Conflicting nested definition for", defKey, "found in schema", schemaName); - console.log(columnizedObjectDiff(nestedDefinitions[defKey], schema.definitions[defKey], true)); - } else { - // console.log("Definition", defKey, "from schema", schemaName, "is identical to existing definition, skipping."); - schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey)); - process.stdout.write(greenBright("M")); - } - } - if (Object.keys(schema.definitions).length === 0) { - process.stdout.write(greenBright("✓ ")); - delete schema.definitions; - } else { - console.log("Remaining definitions in schema", schemaName, "after merge:", Object.keys(schema.definitions)); - } - } - } - mergeDefs(name, part); + // testing: + function mergeDefs(schemaName, schema) { + if (schema.definitions) { + // schema["x-sb-defs"] = Object.keys(schema.definitions); + process.stdout.write(cyanBright("Processing nested... ")); + for (const defKey in schema.definitions) { + if (definitions[defKey] && deepEqual(definitions[defKey], schema.definitions[defKey])) { + // console.log("Definition", defKey, "from schema", schemaName, "is identical to existing definition, skipping."); + schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey)); + process.stdout.write(greenBright("T")); + } else if (!nestedDefinitions[defKey]) { + nestedDefinitions[defKey] = schema.definitions[defKey]; + schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey)); + // console.log("Tracking sub-definition", defKey, "from schema", schemaName); + process.stdout.write(green("N")); + } else if (!deepEqual(nestedDefinitions[defKey], schema.definitions[defKey])) { + console.log(redBright("[ERROR]"), "Conflicting nested definition for", defKey, "found in schema", schemaName); + console.log(columnizedObjectDiff(nestedDefinitions[defKey], schema.definitions[defKey], true)); + } else { + // console.log("Definition", defKey, "from schema", schemaName, "is identical to existing definition, skipping."); + schema.definitions = Object.fromEntries(Object.entries(schema.definitions).filter(([k, v]) => k !== defKey)); + process.stdout.write(greenBright("M")); + } + } + if (Object.keys(schema.definitions).length === 0) { + process.stdout.write(greenBright("✓ ")); + delete schema.definitions; + } else { + console.log("Remaining definitions in schema", schemaName, "after merge:", Object.keys(schema.definitions)); + } + } + } + mergeDefs(name, part); - const elapsed = schemaSw.getElapsedAndReset(); - process.stdout.write( - "Done in " + yellowBright(elapsed.totalMilliseconds + "." + elapsed.microseconds) + " ms, " + yellowBright(JSON.stringify(part).length) + " bytes (unformatted) ", - ); - if (elapsed.totalMilliseconds >= 20) console.log(bgRedBright("\x1b[5m[SLOW]\x1b[25m")); - else console.log(); + const elapsed = schemaSw.getElapsedAndReset(); + process.stdout.write( + "Done in " + yellowBright(elapsed.totalMilliseconds + "." + elapsed.microseconds) + " ms, " + yellowBright(JSON.stringify(part).length) + " bytes (unformatted) ", + ); + if (elapsed.totalMilliseconds >= 20) console.log(bgRedBright("\x1b[5m[SLOW]\x1b[25m")); + else console.log(); - definitions = { ...definitions, [name]: { ...part } }; - } - console.log("Processed", Object.keys(definitions).length, "schemas in", Number(stepSw.elapsed().totalMilliseconds + "." + stepSw.elapsed().microseconds), "ms."); + definitions = { ...definitions, [name]: { ...part } }; + } + console.log("Processed", Object.keys(definitions).length, "schemas in", Number(stepSw.elapsed().totalMilliseconds + "." + stepSw.elapsed().microseconds), "ms."); - console.log("Merging nested definitions into main definitions..."); - let isNewLine = true; - for (const defKey in nestedDefinitions) { - if (!includesMatch(defKey, Included, false)) { - const bannedMatch = includesMatch(defKey, ExcludeAndWarn, false) ?? includesMatch(defKey, Excluded, false); - if (bannedMatch !== null) { - // console.log(yellowBright("\n[WARN]"), "Skipping nested definition", defKey, "as it matched a banned format."); - console.log((isNewLine ? "" : "\n") + redBright("WARNING") + " Excluding schema " + yellowBright(defKey) + " due to match with " + redBright(bannedMatch)); - isNewLine = true; - continue; - } - } + console.log("Merging nested definitions into main definitions..."); + let isNewLine = true; + for (const defKey in nestedDefinitions) { + if (!includesMatch(defKey, Included, false)) { + const bannedMatch = includesMatch(defKey, ExcludeAndWarn, false) ?? includesMatch(defKey, Excluded, false); + if (bannedMatch !== null) { + // console.log(yellowBright("\n[WARN]"), "Skipping nested definition", defKey, "as it matched a banned format."); + console.log((isNewLine ? "" : "\n") + redBright("WARNING") + " Excluding schema " + yellowBright(defKey) + " due to match with " + redBright(bannedMatch)); + isNewLine = true; + continue; + } + } - nestedDefinitions[defKey]["$schema"] = "http://json-schema.org/draft-07/schema#"; - if (definitions[defKey]) { - if (!deepEqual(definitions[defKey], nestedDefinitions[defKey])) { - if (Object.keys(definitions[defKey]).every((k) => k === "$ref" || k === "$schema")) { - definitions[defKey] = nestedDefinitions[defKey]; - console.log(yellowBright("\nWARNING"), "Overwriting definition for", defKey, "with nested definition (ref/schema only)."); - isNewLine = true; - } else { - console.log(redBright("\nERROR"), "Conflicting definition for", defKey, "found in main definitions."); - console.log(columnizedObjectDiff(definitions[defKey], nestedDefinitions[defKey], true)); - console.log("Keys:", Object.keys(definitions[defKey]), Object.keys(nestedDefinitions[defKey])); - isNewLine = true; - } - } else { - // console.log("Definition", defKey, "is identical to existing definition, skipping."); - } - } else { - definitions[defKey] = nestedDefinitions[defKey]; - if (isNewLine) { - process.stdout.write("Adding nested definitions to main definitions: "); - isNewLine = false; - } else process.stdout.write("\x1b[4D, "); - process.stdout.write(yellowBright(defKey) + "... "); - } - } + nestedDefinitions[defKey]["$schema"] = "http://json-schema.org/draft-07/schema#"; + if (definitions[defKey]) { + if (!deepEqual(definitions[defKey], nestedDefinitions[defKey])) { + if (Object.keys(definitions[defKey]).every((k) => k === "$ref" || k === "$schema")) { + definitions[defKey] = nestedDefinitions[defKey]; + console.log(yellowBright("\nWARNING"), "Overwriting definition for", defKey, "with nested definition (ref/schema only)."); + isNewLine = true; + } else { + console.log(redBright("\nERROR"), "Conflicting definition for", defKey, "found in main definitions."); + console.log(columnizedObjectDiff(definitions[defKey], nestedDefinitions[defKey], true)); + console.log("Keys:", Object.keys(definitions[defKey]), Object.keys(nestedDefinitions[defKey])); + isNewLine = true; + } + } else { + // console.log("Definition", defKey, "is identical to existing definition, skipping."); + } + } else { + definitions[defKey] = nestedDefinitions[defKey]; + if (isNewLine) { + process.stdout.write("Adding nested definitions to main definitions: "); + isNewLine = false; + } else process.stdout.write("\x1b[4D, "); + process.stdout.write(yellowBright(defKey) + "... "); + } + } - deleteOneOfKindUndefinedRecursive(definitions, "$"); - for (const defKey in definitions) { - filterSchema(definitions[defKey]); - } + deleteOneOfKindUndefinedRecursive(definitions, "$"); + for (const defKey in definitions) { + filterSchema(definitions[defKey]); + } - if (process.env.WRITE_SCHEMA_DIR === "true") { - await Promise.all(writePromises); - await Promise.all( - Object.keys(definitions).map(async (name) => { - await fsp.writeFile(path.join("schemas_final", `${name}.json`), JSON.stringify(definitions[name], null, 4)); - // console.log("Wrote schema", name, "to schemas/"); - }), - ); - await Promise.all( - Object.keys(nestedDefinitions).map(async (name) => { - await fsp.writeFile(path.join("schemas_nested", `${name}.json`), JSON.stringify(nestedDefinitions[name], null, 4)); - // console.log("Wrote schema", name, "to schemas_nested/"); - }), - ); - } + if (process.env.WRITE_SCHEMA_DIR === "true") { + await Promise.all(writePromises); + await Promise.all( + Object.keys(definitions).map(async (name) => { + await fsp.writeFile(path.join("schemas_final", `${name}.json`), JSON.stringify(definitions[name], null, 4)); + // console.log("Wrote schema", name, "to schemas/"); + }), + ); + await Promise.all( + Object.keys(nestedDefinitions).map(async (name) => { + await fsp.writeFile(path.join("schemas_nested", `${name}.json`), JSON.stringify(nestedDefinitions[name], null, 4)); + // console.log("Wrote schema", name, "to schemas_nested/"); + }), + ); + } - fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); - fs.writeFileSync(__dirname + "/schemaExclusions.json", JSON.stringify(exclusionList, null, 4)); - const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds); - console.log("\nSuccessfully wrote", Object.keys(definitions).length, "schemas to", schemaPath, "in", elapsedMs, "ms,", fs.statSync(schemaPath).size, "bytes."); + fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4)); + fs.writeFileSync(__dirname + "/schemaExclusions.json", JSON.stringify(exclusionList, null, 4)); + const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds); + console.log("\nSuccessfully wrote", Object.keys(definitions).length, "schemas to", schemaPath, "in", elapsedMs, "ms,", fs.statSync(schemaPath).size, "bytes."); } function deleteOneOfKindUndefinedRecursive(obj, path) { - if (obj?.type === "object" && obj?.properties?.oneofKind?.type === "undefined") return true; + if (obj?.type === "object" && obj?.properties?.oneofKind?.type === "undefined") return true; - for (const key in obj) { - if (typeof obj[key] === "object" && deleteOneOfKindUndefinedRecursive(obj[key], path + "." + key)) { - console.log("Deleting", path, key); - delete obj[key]; - } - } + for (const key in obj) { + if (typeof obj[key] === "object" && deleteOneOfKindUndefinedRecursive(obj[key], path + "." + key)) { + console.log("Deleting", path, key); + delete obj[key]; + } + } - return false; + return false; } function filterSchema(schema) { - // this is a hack. we may want to check if its a @column instead - if (schema.properties) { - for (let key in schema.properties) { - if (baseClassProperties.includes(key)) { - delete schema.properties[key]; - } - } - } + // this is a hack. we may want to check if its a @column instead + if (schema.properties) { + for (let key in schema.properties) { + if (baseClassProperties.includes(key)) { + delete schema.properties[key]; + } + } + } - if (schema.required) schema.required = schema.required.filter((x) => !baseClassProperties.includes(x)); + if (schema.required) schema.required = schema.required.filter((x) => !baseClassProperties.includes(x)); - // recurse into own definitions - if (schema.definitions) { - console.log(redBright("WARNING"), "Schema has own definitions, recursing into them to filter base class properties:", Object.keys(schema.definitions)); - for (const defKey in schema.definitions) { - filterSchema(schema.definitions[defKey]); - } - } + // recurse into own definitions + if (schema.definitions) { + console.log(redBright("WARNING"), "Schema has own definitions, recursing into them to filter base class properties:", Object.keys(schema.definitions)); + for (const defKey in schema.definitions) { + filterSchema(schema.definitions[defKey]); + } + } } function deepEqual(a, b) { - if (a === b) return true; + if (a === b) return true; - if (typeof a !== "object" || typeof b !== "object" || a == null || b == null) { - return false; - } + if (typeof a !== "object" || typeof b !== "object" || a == null || b == null) { + return false; + } - const keysA = Object.keys(a); - const keysB = Object.keys(b); + const keysA = Object.keys(a); + const keysB = Object.keys(b); - if (keysA.length !== keysB.length) return false; + if (keysA.length !== keysB.length) return false; - for (const key of keysA) { - if (!keysB.includes(key) || (typeof a[key] === typeof b[key] && !deepEqual(a[key], b[key]))) { - return false; - } - } + for (const key of keysA) { + if (!keysB.includes(key) || (typeof a[key] === typeof b[key] && !deepEqual(a[key], b[key]))) { + return false; + } + } - return true; + return true; } function columnizedObjectDiff(a, b, trackEqual = false) { - const diffs = { left: {}, right: {}, ...(trackEqual ? { equal: {} } : {}) }; - const keys = new Set([...Object.keys(a), ...Object.keys(b)]); - for (const key of keys) { - if (!deepEqual(a[key], b[key])) { - diffs.left[key] = a[key]; - diffs.right[key] = b[key]; - } else if (trackEqual) diffs.equal[key] = a[key]; - } - return diffs; + const diffs = { left: {}, right: {}, ...(trackEqual ? { equal: {} } : {}) }; + const keys = new Set([...Object.keys(a), ...Object.keys(b)]); + for (const key of keys) { + if (!deepEqual(a[key], b[key])) { + diffs.left[key] = a[key]; + diffs.right[key] = b[key]; + } else if (trackEqual) diffs.equal[key] = a[key]; + } + return diffs; } main(); diff --git a/scripts/schemaExclusions.json b/scripts/schemaExclusions.json index 389cb4f..64e2bbf 100644 --- a/scripts/schemaExclusions.json +++ b/scripts/schemaExclusions.json @@ -1,409 +1,415 @@ { - "include": ["MessageInteractionSchema"], - "includeRe": ["^MessageComponentType\\..*"], - "manual": [ - "DefaultSchema", - "Schema", - "EntitySchema", - "ReadableStream", - "SomeJSONSchema", - "UncheckedPartialSchema", - "PartialSchema", - "UncheckedPropertiesSchema", - "PropertiesSchema", - "AsyncSchema", - "AnySchema", - "SMTPConnection.CustomAuthenticationResponse", - "TransportMakeRequestResponse", - "StaticSchema", - "CSVImportResponse", - "NewMultipleMembersResponse", - "EventsResponse", - "NotificationAPIResponse", - "MetricsAPIResponse", - "ValidationResponse", - "MultipleValidationJobsListResponse", - "UpdateRouteResponse", - "MessagesSendAPIResponse", - "APIWebhook", - "WebhookResponse", - "WebhookValidationResponse", - "MessageResponse", - "ConnectionSettingsResponse", - "UpdatedDKIMSelectorResponse", - "UpdatedWebPrefixResponse", - "APIResponse", - "APIErrorOptions", - "APIErrorType", - "ListTagsForResourceResponse", - "GetContactResponse", - "LibraryResponse", - "LibraryLocalResponse", - "SchemaTraits", - "TraitsSchema", - "AbuseIpDbCheckResponse", - "IpDataIpLookupResponse" - ], - "manualRe": [ - ".*\\.Response$", - "^(Http2Server|Server|Express|(Resolved|)Http|Client|_|)Response$", - ".*\\..*", - "^Axios.*", - "^Internal", - "^Record<", - "^Omit<", - "^ListContact(s|Lists)Response$", - "^APIKeyConfiguration\\..*", - "^AccountSetting\\..*", - "^BulkContactManagement\\..*", - "^Campaign.*", - "^Contact.*", - "^DNS\\..*", - "^Delete.*", - "^Destroy.*", - "^Template\\..*", - "^Webhook\\..*", - "^(BigDecimal|BigInteger|Blob|Boolean|Document|Error|LazyRequest|List|Map|Normalized|Numeric|StreamingBlob|TimestampDateTime|TimestampHttpDate|TimestampEpochSeconds|Simple)Schema", - "^((Create|Update)Contact(|List))Response$", - "^(T|Unt)agResourceResponse$", - "^Put", - "^Inbox", - "^Seed", - "^DomainTag", - "^IpPool", - "DomainTemplate", - "^\\$", - "^Suppression", - "^Mail(|ing)List", - "DomainTracking", - "UpdatedDomain", - "ConfigurationSet", - "ContactList", - "^IPR", - "^Job" - ], - "manualWarn": [], - "manualWarnRe": [".*<.*>$"], - "auto": [ - { - "value": "StringSchema", - "reason": "AWS Smithy schema" - }, - { - "value": "TimestampDefaultSchema", - "reason": "AWS Smithy schema" - }, - { - "value": "StaticSimpleSchema", - "reason": "Self-reference only schema" - }, - { - "value": "StaticListSchema", - "reason": "Self-reference only schema" - }, - { - "value": "StaticMapSchema", - "reason": "Self-reference only schema" - }, - { - "value": "StaticStructureSchema", - "reason": "Self-reference only schema" - }, - { - "value": "StaticErrorSchema", - "reason": "Self-reference only schema" - }, - { - "value": "StaticOperationSchema", - "reason": "Self-reference only schema" - }, - { - "value": "UnitSchema", - "reason": "AWS Smithy schema" - }, - { - "value": "MemberSchema", - "reason": "Self-reference only schema" - }, - { - "value": "StructureSchema", - "reason": "Self-reference only schema" - }, - { - "value": "OperationSchema", - "reason": "Self-reference only schema" - }, - { - "value": "BatchGetMetricDataResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CancelExportJobResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateCustomVerificationEmailTemplateResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateDedicatedIpPoolResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateDeliverabilityTestReportResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateEmailIdentityResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateEmailIdentityPolicyResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateEmailTemplateResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateExportJobResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateImportJobResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateMultiRegionEndpointResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateTenantResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "CreateTenantResourceAssociationResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetAccountResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetBlacklistReportsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetCustomVerificationEmailTemplateResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetDedicatedIpResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetDedicatedIpPoolResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetDedicatedIpsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetDeliverabilityDashboardOptionsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetDeliverabilityTestReportResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetDomainDeliverabilityCampaignResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetDomainStatisticsReportResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetEmailIdentityResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetEmailIdentityPoliciesResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetEmailTemplateResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetExportJobResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetImportJobResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetMessageInsightsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetMultiRegionEndpointResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetReputationEntityResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetSuppressedDestinationResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "GetTenantResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListCustomVerificationEmailTemplatesResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListDedicatedIpPoolsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListDeliverabilityTestReportsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListDomainDeliverabilityCampaignsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListEmailIdentitiesResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListEmailTemplatesResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListExportJobsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListImportJobsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListMultiRegionEndpointsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListRecommendationsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListReputationEntitiesResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListResourceTenantsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListSuppressedDestinationsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListTenantResourcesResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "ListTenantsResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "SendBulkEmailResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "SendCustomVerificationEmailResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "SendEmailResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "TestRenderEmailTemplateResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "UpdateCustomVerificationEmailTemplateResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "UpdateEmailIdentityPolicyResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "UpdateEmailTemplateResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "UpdateReputationEntityCustomerManagedStatusResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "UpdateReputationEntityPolicyResponse", - "reason": "HTML paragraph in description" - }, - { - "value": "UpdatedDKIMAuthorityResponse", - "reason": "Uses import()" - }, - { - "value": "ListRecipientResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "TemplateResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "TemplateDetailContentResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "EventCallbackUrlResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "ParseRouteResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "SenderResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "MetaSenderResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "ApiKeyResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "MyProfileResponse", - "reason": "Schema with only uppercase properties" - }, - { - "value": "UserResponse", - "reason": "Schema with only uppercase properties" - } - ] -} + "include": [ + "MessageInteractionSchema" + ], + "includeRe": [ + "^MessageComponentType\\..*" + ], + "manual": [ + "DefaultSchema", + "Schema", + "EntitySchema", + "ReadableStream", + "SomeJSONSchema", + "UncheckedPartialSchema", + "PartialSchema", + "UncheckedPropertiesSchema", + "PropertiesSchema", + "AsyncSchema", + "AnySchema", + "SMTPConnection.CustomAuthenticationResponse", + "TransportMakeRequestResponse", + "StaticSchema", + "CSVImportResponse", + "NewMultipleMembersResponse", + "EventsResponse", + "NotificationAPIResponse", + "MetricsAPIResponse", + "ValidationResponse", + "MultipleValidationJobsListResponse", + "UpdateRouteResponse", + "MessagesSendAPIResponse", + "APIWebhook", + "WebhookResponse", + "WebhookValidationResponse", + "MessageResponse", + "ConnectionSettingsResponse", + "UpdatedDKIMSelectorResponse", + "UpdatedWebPrefixResponse", + "APIResponse", + "APIErrorOptions", + "APIErrorType", + "ListTagsForResourceResponse", + "GetContactResponse", + "LibraryResponse", + "LibraryLocalResponse", + "SchemaTraits", + "TraitsSchema", + "AbuseIpDbCheckResponse", + "IpDataIpLookupResponse" + ], + "manualRe": [ + ".*\\.Response$", + "^(Http2Server|Server|Express|(Resolved|)Http|Client|_|)Response$", + ".*\\..*", + "^Axios.*", + "^Internal", + "^Record<", + "^Omit<", + "^ListContact(s|Lists)Response$", + "^APIKeyConfiguration\\..*", + "^AccountSetting\\..*", + "^BulkContactManagement\\..*", + "^Campaign.*", + "^Contact.*", + "^DNS\\..*", + "^Delete.*", + "^Destroy.*", + "^Template\\..*", + "^Webhook\\..*", + "^(BigDecimal|BigInteger|Blob|Boolean|Document|Error|LazyRequest|List|Map|Normalized|Numeric|StreamingBlob|TimestampDateTime|TimestampHttpDate|TimestampEpochSeconds|Simple)Schema", + "^((Create|Update)Contact(|List))Response$", + "^(T|Unt)agResourceResponse$", + "^Put", + "^Inbox", + "^Seed", + "^DomainTag", + "^IpPool", + "DomainTemplate", + "^\\$", + "^Suppression", + "^Mail(|ing)List", + "DomainTracking", + "UpdatedDomain", + "ConfigurationSet", + "ContactList", + "^IPR", + "^Job" + ], + "manualWarn": [], + "manualWarnRe": [ + ".*<.*>$" + ], + "auto": [ + { + "value": "StringSchema", + "reason": "AWS Smithy schema" + }, + { + "value": "TimestampDefaultSchema", + "reason": "AWS Smithy schema" + }, + { + "value": "StaticSimpleSchema", + "reason": "Self-reference only schema" + }, + { + "value": "StaticListSchema", + "reason": "Self-reference only schema" + }, + { + "value": "StaticMapSchema", + "reason": "Self-reference only schema" + }, + { + "value": "StaticStructureSchema", + "reason": "Self-reference only schema" + }, + { + "value": "StaticErrorSchema", + "reason": "Self-reference only schema" + }, + { + "value": "StaticOperationSchema", + "reason": "Self-reference only schema" + }, + { + "value": "UnitSchema", + "reason": "AWS Smithy schema" + }, + { + "value": "MemberSchema", + "reason": "Self-reference only schema" + }, + { + "value": "StructureSchema", + "reason": "Self-reference only schema" + }, + { + "value": "OperationSchema", + "reason": "Self-reference only schema" + }, + { + "value": "BatchGetMetricDataResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CancelExportJobResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateCustomVerificationEmailTemplateResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateDedicatedIpPoolResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateDeliverabilityTestReportResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateEmailIdentityResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateEmailIdentityPolicyResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateEmailTemplateResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateExportJobResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateImportJobResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateMultiRegionEndpointResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateTenantResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "CreateTenantResourceAssociationResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetAccountResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetBlacklistReportsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetCustomVerificationEmailTemplateResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetDedicatedIpResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetDedicatedIpPoolResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetDedicatedIpsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetDeliverabilityDashboardOptionsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetDeliverabilityTestReportResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetDomainDeliverabilityCampaignResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetDomainStatisticsReportResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetEmailIdentityResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetEmailIdentityPoliciesResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetEmailTemplateResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetExportJobResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetImportJobResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetMessageInsightsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetMultiRegionEndpointResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetReputationEntityResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetSuppressedDestinationResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "GetTenantResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListCustomVerificationEmailTemplatesResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListDedicatedIpPoolsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListDeliverabilityTestReportsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListDomainDeliverabilityCampaignsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListEmailIdentitiesResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListEmailTemplatesResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListExportJobsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListImportJobsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListMultiRegionEndpointsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListRecommendationsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListReputationEntitiesResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListResourceTenantsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListSuppressedDestinationsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListTenantResourcesResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "ListTenantsResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "SendBulkEmailResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "SendCustomVerificationEmailResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "SendEmailResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "TestRenderEmailTemplateResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "UpdateCustomVerificationEmailTemplateResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "UpdateEmailIdentityPolicyResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "UpdateEmailTemplateResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "UpdateReputationEntityCustomerManagedStatusResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "UpdateReputationEntityPolicyResponse", + "reason": "HTML paragraph in description" + }, + { + "value": "UpdatedDKIMAuthorityResponse", + "reason": "Uses import()" + }, + { + "value": "ListRecipientResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "TemplateResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "TemplateDetailContentResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "EventCallbackUrlResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "ParseRouteResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "SenderResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "MetaSenderResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "ApiKeyResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "MyProfileResponse", + "reason": "Schema with only uppercase properties" + }, + { + "value": "UserResponse", + "reason": "Schema with only uppercase properties" + } + ] +} \ No newline at end of file diff --git a/scripts/stress/identify.js b/scripts/stress/identify.js index 478643f..08d8d5d 100644 --- a/scripts/stress/identify.js +++ b/scripts/stress/identify.js @@ -8,45 +8,45 @@ const TOTAL_ITERATIONS = process.env.ITER ? parseInt(process.env.ITER) : 500; const doTimedIdentify = () => - new Promise((resolve) => { - let start; - const ws = new WebSocket(ENDPOINT); - ws.on("message", (data) => { - const parsed = JSON.parse(data); + new Promise((resolve) => { + let start; + const ws = new WebSocket(ENDPOINT); + ws.on("message", (data) => { + const parsed = JSON.parse(data); - switch (parsed.op) { - case OPCODES.Hello: - // send identify - start = performance.now(); - ws.send( - JSON.stringify({ - op: OPCODES.Identify, - d: { - token: TOKEN, - properties: {}, - }, - }), - ); - break; - case OPCODES.Dispatch: - if (parsed.t == "READY") { - ws.close(); - return resolve(performance.now() - start); - } + switch (parsed.op) { + case OPCODES.Hello: + // send identify + start = performance.now(); + ws.send( + JSON.stringify({ + op: OPCODES.Identify, + d: { + token: TOKEN, + properties: {}, + }, + }), + ); + break; + case OPCODES.Dispatch: + if (parsed.t == "READY") { + ws.close(); + return resolve(performance.now() - start); + } - break; - } - }); - }); + break; + } + }); + }); (async () => { - const perfs = []; - while (perfs.length < TOTAL_ITERATIONS) { - const ret = await doTimedIdentify(); - perfs.push(ret); - // console.log(`${perfs.length}/${TOTAL_ITERATIONS} - this: ${Math.floor(ret)}ms`) - } + const perfs = []; + while (perfs.length < TOTAL_ITERATIONS) { + const ret = await doTimedIdentify(); + perfs.push(ret); + // console.log(`${perfs.length}/${TOTAL_ITERATIONS} - this: ${Math.floor(ret)}ms`) + } - const avg = perfs.reduce((prev, curr) => prev + curr) / (perfs.length - 1); - console.log(`Average identify time: ${Math.floor(avg * 100) / 100}ms`); + const avg = perfs.reduce((prev, curr) => prev + curr) / (perfs.length - 1); + console.log(`Average identify time: ${Math.floor(avg * 100) / 100}ms`); })(); diff --git a/scripts/stress/login.js b/scripts/stress/login.js index 5cb254d..830d6a4 100644 --- a/scripts/stress/login.js +++ b/scripts/stress/login.js @@ -1,16 +1,16 @@ const ENDPOINT = process.env.API || "http://localhost:3001"; async function main() { - const ret = await fetch(`${ENDPOINT}/api/auth/login`, { - method: "POST", - body: JSON.stringify({ - login: process.argv[2], - password: process.argv[3], - }), - headers: { "content-type": "application/json " }, - }); + const ret = await fetch(`${ENDPOINT}/api/auth/login`, { + method: "POST", + body: JSON.stringify({ + login: process.argv[2], + password: process.argv[3], + }), + headers: { "content-type": "application/json " }, + }); - console.log((await ret.json()).token); + console.log((await ret.json()).token); } main(); diff --git a/scripts/stress/users.js b/scripts/stress/users.js index 67fa3ec..2fc41ea 100644 --- a/scripts/stress/users.js +++ b/scripts/stress/users.js @@ -21,22 +21,22 @@ const endpoint = process.env.API || "http://localhost:3001"; async function main() { - for (let i = 0; i < count; i++) { - fetch(`${endpoint}/api/auth/register`, { - method: "POST", - body: JSON.stringify({ - fingerprint: `${i}.wR8vi8lGlFBJerErO9LG5NViJFw`, - username: `test${i}`, - invite: null, - consent: true, - date_of_birth: "2000-01-01", - gift_code_sku_id: null, - captcha_key: null, - }), - headers: { "content-type": "application/json" }, - }); - console.log(i); - } + for (let i = 0; i < count; i++) { + fetch(`${endpoint}/api/auth/register`, { + method: "POST", + body: JSON.stringify({ + fingerprint: `${i}.wR8vi8lGlFBJerErO9LG5NViJFw`, + username: `test${i}`, + invite: null, + consent: true, + date_of_birth: "2000-01-01", + gift_code_sku_id: null, + captcha_key: null, + }), + headers: { "content-type": "application/json" }, + }); + console.log(i); + } } main(); diff --git a/scripts/syncronise.js b/scripts/syncronise.js index 1f3ed11..58c1802 100644 --- a/scripts/syncronise.js +++ b/scripts/syncronise.js @@ -30,9 +30,9 @@ const { initDatabase } = require(".."); (async () => { - const db = await initDatabase(); - console.log("synchronising"); - await db.synchronize(); - console.log("done"); - db.destroy(); + const db = await initDatabase(); + console.log("synchronising"); + await db.synchronize(); + console.log("done"); + db.destroy(); })(); diff --git a/scripts/test.js b/scripts/test.js index c79e336..3f94413 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -29,34 +29,34 @@ process.env.CONFIG_PATH = cfgFile; fs.writeFileSync( - cfgFile, - JSON.stringify({ - api: { endpointPublic: "http://localhost:3001/api/v9/" }, - cdn: { endpointPublic: "http://localhost:3001/", endpointPrivate: "http://localhost:3001/" }, - gateway: { endpointPublic: "ws://localhost:3001/" }, - }), + cfgFile, + JSON.stringify({ + api: { endpointPublic: "http://localhost:3001/api/v9/" }, + cdn: { endpointPublic: "http://localhost:3001/", endpointPrivate: "http://localhost:3001/" }, + gateway: { endpointPublic: "ws://localhost:3001/" }, + }), ); const server = spawn("node", [path.join(__dirname, "..", "dist", "bundle", "start.js")]); server.stdout.on("data", (data) => { - process.stdout.write(data); + process.stdout.write(data); - if (data.toString().toLowerCase().includes("listening")) { - // we good :) - console.log("we good"); - server.kill(); - process.exit(); - } + if (data.toString().toLowerCase().includes("listening")) { + // we good :) + console.log("we good"); + server.kill(); + process.exit(); + } }); server.stderr.on("data", (err) => { - process.stdout.write(err); - // we bad :( - process.kill(1); + process.stdout.write(err); + // we bad :( + process.kill(1); }); server.on("close", (code) => { - console.log("closed with code", code); - process.exit(code); + console.log("closed with code", code); + process.exit(code); }); diff --git a/scripts/util/getRouteDescriptions.js b/scripts/util/getRouteDescriptions.js index 1f27141..87f1d4e 100644 --- a/scripts/util/getRouteDescriptions.js +++ b/scripts/util/getRouteDescriptions.js @@ -15,24 +15,24 @@ */ function colorizeMethod(method) { - switch (method.toLowerCase()) { - case "get": - return greenBright(method.toUpperCase()); - case "post": - return yellowBright(method.toUpperCase()); - case "put": - return blueBright(method.toUpperCase()); - case "delete": - return redBright(method.toUpperCase()); - case "patch": - return yellowBright(method.toUpperCase()); - default: - return method.toUpperCase(); - } + switch (method.toLowerCase()) { + case "get": + return greenBright(method.toUpperCase()); + case "post": + return yellowBright(method.toUpperCase()); + case "put": + return blueBright(method.toUpperCase()); + case "delete": + return redBright(method.toUpperCase()); + case "patch": + return yellowBright(method.toUpperCase()); + default: + return method.toUpperCase(); + } } function formatPath(path) { - return path.replace(/:(\w+)/g, underline(":$1")).replace(/#(\w+)/g, underline("#$1")); + return path.replace(/:(\w+)/g, underline(":$1")).replace(/#(\w+)/g, underline("#$1")); } /** @@ -43,45 +43,45 @@ * @param args */ function proxy(file, apiMethod, apiPathPrefix, apiPath, ...args) { - const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true); - if (!opts) - return console.error( - ` \x1b[5m${bgRedBright("ERROR")}\x1b[25m ${file.replace(path.resolve(__dirname, "..", "..", "dist"), "/src")} has route without route() description middleware: ${colorizeMethod(apiMethod)} ${formatPath(apiPath)}`, - ); + const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true); + if (!opts) + return console.error( + ` \x1b[5m${bgRedBright("ERROR")}\x1b[25m ${file.replace(path.resolve(__dirname, "..", "..", "dist"), "/src")} has route without route() description middleware: ${colorizeMethod(apiMethod)} ${formatPath(apiPath)}`, + ); - console.log(`${colorizeMethod(apiMethod).padStart("DELETE".length + 10)} ${formatPath(apiPathPrefix + apiPath)}`); - opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts"); - routes.set(apiPathPrefix + apiPath + "|" + apiMethod, opts()); + console.log(`${colorizeMethod(apiMethod).padStart("DELETE".length + 10)} ${formatPath(apiPathPrefix + apiPath)}`); + opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts"); + routes.set(apiPathPrefix + apiPath + "|" + apiMethod, opts()); } express.Router = () => { - return Object.fromEntries(methods.map((method) => [method, proxy.bind(null, currentFile, method, currentPath)])); + return Object.fromEntries(methods.map((method) => [method, proxy.bind(null, currentFile, method, currentPath)])); }; RouteUtility.route = (opts) => { - const func = function () { - return opts; - }; - func.prototype.OPTS_MARKER = true; - return func; + const func = function () { + return opts; + }; + func.prototype.OPTS_MARKER = true; + return func; }; module.exports = function getRouteDescriptions() { - const root = path.join(__dirname, "..", "..", "dist", "api", "routes", "/"); - traverseDirectory({ dirname: root, recursive: true }, (file) => { - currentFile = file; + const root = path.join(__dirname, "..", "..", "dist", "api", "routes", "/"); + traverseDirectory({ dirname: root, recursive: true }, (file) => { + currentFile = file; - currentPath = file.replace(root.slice(0, -1), ""); - currentPath = currentPath.split(".").slice(0, -1).join("."); // truncate .js/.ts file extension of path - currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes - if (currentPath.endsWith("/index")) currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path + currentPath = file.replace(root.slice(0, -1), ""); + currentPath = currentPath.split(".").slice(0, -1).join("."); // truncate .js/.ts file extension of path + currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes + if (currentPath.endsWith("/index")) currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path - try { - require(file); - } catch (e) { - console.error(e); - } - }); + try { + require(file); + } catch (e) { + console.error(e); + } + }); - return routes; + return routes; }; diff --git a/scripts/util/walk.js b/scripts/util/walk.js index be59023..0654331 100644 --- a/scripts/util/walk.js +++ b/scripts/util/walk.js @@ -20,18 +20,18 @@ /** dir: string. types: string[] ( file types ) */ module.exports = function walk(dir, types = ["ts"]) { - var results = []; - var list = fs.readdirSync(dir); - list.forEach(function (file) { - file = dir + "/" + file; - var stat = fs.statSync(file); - if (stat && stat.isDirectory()) { - /* Recurse into a subdirectory */ - results = results.concat(walk(file, types)); - } else { - if (!types.find((x) => file.endsWith(x))) return; - results.push(file); - } - }); - return results; + var results = []; + var list = fs.readdirSync(dir); + list.forEach(function (file) { + file = dir + "/" + file; + var stat = fs.statSync(file); + if (stat && stat.isDirectory()) { + /* Recurse into a subdirectory */ + results = results.concat(walk(file, types)); + } else { + if (!types.find((x) => file.endsWith(x))) return; + results.push(file); + } + }); + return results; }; diff --git a/server.code-workspace b/server.code-workspace index c3ffcff..d73553d 100644 --- a/server.code-workspace +++ b/server.code-workspace @@ -1,23 +1,23 @@ { - "folders": [ - { - "path": "src", - }, - { - "path": "assets", - }, - { - "path": "scripts", - }, - { - "path": ".", - }, - ], - "settings": { - "typescript.tsdk": "util\\node_modules\\typescript\\lib", - }, - "launch": { - "version": "0.2.0", - "configurations": [], - }, + "folders": [ + { + "path": "src", + }, + { + "path": "assets", + }, + { + "path": "scripts", + }, + { + "path": ".", + }, + ], + "settings": { + "typescript.tsdk": "util\\node_modules\\typescript\\lib", + }, + "launch": { + "version": "0.2.0", + "configurations": [], + }, } diff --git a/src/api/Server.ts b/src/api/Server.ts index 0969e5e..f81898e 100644 --- a/src/api/Server.ts +++ b/src/api/Server.ts @@ -31,149 +31,149 @@ export type SpacebarServerOptions = ServerOptions; declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - interface Request { - server: SpacebarServer; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + server: SpacebarServer; + } + } } export class SpacebarServer extends Server { - declare public options: SpacebarServerOptions; + declare public options: SpacebarServerOptions; - constructor(opts?: Partial) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - super({ ...opts, errorHandler: false, jsonBody: false }); - } + constructor(opts?: Partial) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + super({ ...opts, errorHandler: false, jsonBody: false }); + } - async start() { - await initDatabase(); - await Config.init(); - await initEvent(); - await Email.init(); - await ConnectionConfig.init(); - await initInstance(); - WebAuthn.init(); + async start() { + await initDatabase(); + await Config.init(); + await initEvent(); + await Email.init(); + await ConnectionConfig.init(); + await initInstance(); + WebAuthn.init(); - const logRequests = process.env["LOG_REQUESTS"] != undefined; - if (logRequests) { - this.app.use( - morgan("combined", { - skip: (req, res) => { - let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); - if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; - return skip; - }, - }), - ); - } + const logRequests = process.env["LOG_REQUESTS"] != undefined; + if (logRequests) { + this.app.use( + morgan("combined", { + skip: (req, res) => { + let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); + if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; + return skip; + }, + }), + ); + } - this.app.set("json replacer", JSONReplacer); - this.app.disable("x-powered-by"); + this.app.set("json replacer", JSONReplacer); + this.app.disable("x-powered-by"); - const trustedProxies = Config.get().security.trustedProxies; - if (trustedProxies) this.app.set("trust proxy", trustedProxies); + const trustedProxies = Config.get().security.trustedProxies; + if (trustedProxies) this.app.set("trust proxy", trustedProxies); - this.app.use(CORS); - this.app.use(BodyParser({ inflate: true, limit: "10mb" })); + this.app.use(CORS); + this.app.use(BodyParser({ inflate: true, limit: "10mb" })); - const app = this.app; - const api = Router({ mergeParams: true }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this.app = api; + const app = this.app; + const api = Router({ mergeParams: true }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.app = api; - api.use(Authentication); - await initRateLimits(api); - await initTranslation(api); + api.use(Authentication); + await initRateLimits(api); + await initTranslation(api); - this.routes = (await registerRoutes(this, path.join(__dirname, "routes", "/"))).filter((r) => !!r); + this.routes = (await registerRoutes(this, path.join(__dirname, "routes", "/"))).filter((r) => !!r); - // 404 is not an error in express, so this should not be an error middleware - // this is a fine place to put the 404 handler because its after we register the routes - // and since its not an error middleware, our error handler below still works. - // Emma [it/its] @ Rory& - the _ is required now, as pillarjs throw an error if you don't pass a param name now - api.use("*_", (req: Request, res: Response) => { - res.status(404).json({ - message: "404 endpoint not found", - code: 0, - }); - }); + // 404 is not an error in express, so this should not be an error middleware + // this is a fine place to put the 404 handler because its after we register the routes + // and since its not an error middleware, our error handler below still works. + // Emma [it/its] @ Rory& - the _ is required now, as pillarjs throw an error if you don't pass a param name now + api.use("*_", (req: Request, res: Response) => { + res.status(404).json({ + message: "404 endpoint not found", + code: 0, + }); + }); - this.app = app; + this.app = app; - //app.use("/__development", ) - //app.use("/__internals", ) - app.use("/api/v6", api); - app.use("/api/v7", api); - app.use("/api/v8", api); - app.use("/api/v9", api); - app.use("/api", api); // allow unversioned requests + //app.use("/__development", ) + //app.use("/__internals", ) + app.use("/api/v6", api); + app.use("/api/v7", api); + app.use("/api/v8", api); + app.use("/api/v9", api); + app.use("/api", api); // allow unversioned requests - app.use("/imageproxy/:hash/:size/:url", ImageProxy); + app.use("/imageproxy/:hash/:size/:url", ImageProxy); - app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html"))); + app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html"))); - app.get("/verify-email", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html"))); + app.get("/verify-email", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html"))); - app.get("/_spacebar/api/schemas.json", (req, res) => { - res.sendFile(path.join(ASSETS_FOLDER, "schemas.json")); - }); + app.get("/_spacebar/api/schemas.json", (req, res) => { + res.sendFile(path.join(ASSETS_FOLDER, "schemas.json")); + }); - app.get("/_spacebar/api/openapi.json", (req, res) => { - res.sendFile(path.join(ASSETS_FOLDER, "openapi.json")); - }); + app.get("/_spacebar/api/openapi.json", (req, res) => { + res.sendFile(path.join(ASSETS_FOLDER, "openapi.json")); + }); - // current well-known location - app.get("/.well-known/spacebar", (req, res) => { - res.json({ - api: Config.get().api.endpointPublic, - }); - }); + // current well-known location + app.get("/.well-known/spacebar", (req, res) => { + res.json({ + api: Config.get().api.endpointPublic, + }); + }); - // new well-known location - app.get("/.well-known/spacebar/client", (req, res) => { - let erlpackSupported = false; - try { - require("@yukikaze-bot/erlpack"); - erlpackSupported = true; - } catch (e) { - // empty - } + // new well-known location + app.get("/.well-known/spacebar/client", (req, res) => { + let erlpackSupported = false; + try { + require("@yukikaze-bot/erlpack"); + erlpackSupported = true; + } catch (e) { + // empty + } - res.json({ - api: { - baseUrl: Config.get().api.endpointPublic?.split("/api")[0] || "", // TODO: migrate database values to not include /api/v9 - apiVersions: { - default: Config.get().api.defaultVersion, - active: Config.get().api.activeVersions, - }, - }, - cdn: { - baseUrl: Config.get().cdn.endpointPublic, - }, - gateway: { - baseUrl: Config.get().gateway.endpointPublic, - encoding: [...(erlpackSupported ? ["etf"] : []), "json"], - compression: ["zstd-stream", "zlib-stream", null], - }, - admin: - Config.get().admin.endpointPublic === null - ? undefined - : { - baseUrl: Config.get().admin.endpointPublic, - }, - }); - }); + res.json({ + api: { + baseUrl: Config.get().api.endpointPublic?.split("/api")[0] || "", // TODO: migrate database values to not include /api/v9 + apiVersions: { + default: Config.get().api.defaultVersion, + active: Config.get().api.activeVersions, + }, + }, + cdn: { + baseUrl: Config.get().cdn.endpointPublic, + }, + gateway: { + baseUrl: Config.get().gateway.endpointPublic, + encoding: [...(erlpackSupported ? ["etf"] : []), "json"], + compression: ["zstd-stream", "zlib-stream", null], + }, + admin: + Config.get().admin.endpointPublic === null + ? undefined + : { + baseUrl: Config.get().admin.endpointPublic, + }, + }); + }); - this.app.use(ErrorHandler); + this.app.use(ErrorHandler); - ConnectionLoader.loadConnections(); + ConnectionLoader.loadConnections(); - if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`)); + if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`)); - return super.start(); - } + return super.start(); + } } diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts index 47f6ab3..078a566 100644 --- a/src/api/middlewares/Authentication.ts +++ b/src/api/middlewares/Authentication.ts @@ -21,112 +21,112 @@ import { HTTPError } from "lambert-server"; export const NO_AUTHORIZATION_ROUTES = [ - // Authentication routes - "POST /auth/login", - "POST /auth/register", - "GET /auth/location-metadata", - "POST /auth/mfa/", - "POST /auth/verify", - "POST /auth/forgot", - "POST /auth/reset", - "POST /auth/fingerprint", - "GET /invites/", - // Routes with a seperate auth system - /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth - /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/, - // Public information endpoints - "GET /ping", - "GET /gateway", - "GET /experiments", - "GET /updates", - "GET /download", - "GET /scheduled-maintenances/upcoming.json", - // Public kubernetes integration - "GET /-/readyz", - "GET /-/healthz", - // Client analytics - "POST /science", - "POST /track", - // Public policy pages - "GET /policies/instance/", - // Oauth callback - "/oauth2/callback", - // Asset delivery - /^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/, - // Connections - /^(POST|HEAD) \/connections\/\w+\/callback/, - // Image proxy - /^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/, + // Authentication routes + "POST /auth/login", + "POST /auth/register", + "GET /auth/location-metadata", + "POST /auth/mfa/", + "POST /auth/verify", + "POST /auth/forgot", + "POST /auth/reset", + "POST /auth/fingerprint", + "GET /invites/", + // Routes with a seperate auth system + /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth + /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/, + // Public information endpoints + "GET /ping", + "GET /gateway", + "GET /experiments", + "GET /updates", + "GET /download", + "GET /scheduled-maintenances/upcoming.json", + // Public kubernetes integration + "GET /-/readyz", + "GET /-/healthz", + // Client analytics + "POST /science", + "POST /track", + // Public policy pages + "GET /policies/instance/", + // Oauth callback + "/oauth2/callback", + // Asset delivery + /^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/, + // Connections + /^(POST|HEAD) \/connections\/\w+\/callback/, + // Image proxy + /^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/, ]; export const API_PREFIX = /^\/api(\/v\d+)?/; export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//; declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - interface Request { - user_id: string; - user_bot: boolean; - token: { id: string; iat: number; ver?: number; did?: string }; - rights: Rights; - fingerprint?: string; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + user_id: string; + user_bot: boolean; + token: { id: string; iat: number; ver?: number; did?: string }; + rights: Rights; + fingerprint?: string; + } + } } export async function Authentication(req: Request, res: Response, next: NextFunction) { - if (req.method === "OPTIONS") return res.sendStatus(204); - const url = req.url.replace(API_PREFIX, ""); + if (req.method === "OPTIONS") return res.sendStatus(204); + const url = req.url.replace(API_PREFIX, ""); - if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) - req.fingerprint = req.headers.cookie - .split("; ") - .find((x) => x.startsWith("__sb_sessid="))! - .split("=")[1]; - // for some reason we need to require here, else the openapi generator fails with "route is not a function" - else res.setHeader("Set-Cookie", `__sb_sessid=${(req.fingerprint = (await require("../util")).randomString(32))}; Secure; HttpOnly; SameSite=None; Path=/`); + if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) + req.fingerprint = req.headers.cookie + .split("; ") + .find((x) => x.startsWith("__sb_sessid="))! + .split("=")[1]; + // for some reason we need to require here, else the openapi generator fails with "route is not a function" + else res.setHeader("Set-Cookie", `__sb_sessid=${(req.fingerprint = (await require("../util")).randomString(32))}; Secure; HttpOnly; SameSite=None; Path=/`); - if ( - NO_AUTHORIZATION_ROUTES.some((x) => { - if (typeof x !== "string") { - return x.test(req.method + " " + url); - } + if ( + NO_AUTHORIZATION_ROUTES.some((x) => { + if (typeof x !== "string") { + return x.test(req.method + " " + url); + } - const fullRoute = req.method + " " + url; + const fullRoute = req.method + " " + url; - if (req.method === "HEAD") { - const urlPart = x.split(" ").slice(1).join(" "); - if (urlPart.endsWith("/")) { - return url.startsWith(urlPart); - } else { - return url === urlPart; - } - } + if (req.method === "HEAD") { + const urlPart = x.split(" ").slice(1).join(" "); + if (urlPart.endsWith("/")) { + return url.startsWith(urlPart); + } else { + return url === urlPart; + } + } - if (x.endsWith("/")) { - return fullRoute.startsWith(x); - } else { - return fullRoute === x; - } - }) - ) - return next(); + if (x.endsWith("/")) { + return fullRoute.startsWith(x); + } else { + return fullRoute === x; + } + }) + ) + return next(); - if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401)); + if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401)); - try { - const { decoded, user, session, tokenVersion } = await checkToken(req.headers.authorization, { - ipAddress: req.ip, - fingerprint: req.fingerprint, - }); + try { + const { decoded, user, session, tokenVersion } = await checkToken(req.headers.authorization, { + ipAddress: req.ip, + fingerprint: req.fingerprint, + }); - req.token = decoded; - req.user_id = decoded.id; - req.user_bot = user.bot; - req.rights = new Rights(Number(user.rights)); - return next(); - } catch (error) { - return next(new HTTPError(error!.toString(), 400)); - } + req.token = decoded; + req.user_id = decoded.id; + req.user_bot = user.bot; + req.rights = new Rights(Number(user.rights)); + return next(); + } catch (error) { + return next(new HTTPError(error!.toString(), 400)); + } } diff --git a/src/api/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts index e08aad4..a579abb 100644 --- a/src/api/middlewares/BodyParser.ts +++ b/src/api/middlewares/BodyParser.ts @@ -21,31 +21,31 @@ import { HTTPError } from "lambert-server"; const errorMessages: { [key: string]: [string, number] } = { - "entity.too.large": ["Request body too large", 413], - "entity.parse.failed": ["Invalid JSON body", 400], - "entity.verify.failed": ["Entity verification failed", 403], - "request.aborted": ["Request aborted", 400], - "request.size.invalid": ["Request size did not match content length", 400], - "stream.encoding.set": ["Stream encoding should not be set", 500], - "stream.not.readable": ["Stream is not readable", 500], - "parameters.too.many": ["Too many parameters", 413], - "charset.unsupported": ["Unsupported charset", 415], - "encoding.unsupported": ["Unsupported content encoding", 415], + "entity.too.large": ["Request body too large", 413], + "entity.parse.failed": ["Invalid JSON body", 400], + "entity.verify.failed": ["Entity verification failed", 403], + "request.aborted": ["Request aborted", 400], + "request.size.invalid": ["Request size did not match content length", 400], + "stream.encoding.set": ["Stream encoding should not be set", 500], + "stream.not.readable": ["Stream is not readable", 500], + "parameters.too.many": ["Too many parameters", 413], + "charset.unsupported": ["Unsupported charset", 415], + "encoding.unsupported": ["Unsupported content encoding", 415], }; export function BodyParser(opts?: OptionsJson) { - const jsonParser = bodyParser.json(opts); + const jsonParser = bodyParser.json(opts); - return (req: Request, res: Response, next: NextFunction) => { - if (!req.headers["content-type"]) req.headers["content-type"] = "application/json"; + return (req: Request, res: Response, next: NextFunction) => { + if (!req.headers["content-type"]) req.headers["content-type"] = "application/json"; - jsonParser(req, res, (err) => { - if (err) { - const [message, status] = errorMessages[err.type] || ["Invalid Body", 400]; - const errorMessage = message.includes("charset") || message.includes("encoding") ? `${message} "${err.charset || err.encoding}"` : message; - return next(new HTTPError(errorMessage, status)); - } - next(); - }); - }; + jsonParser(req, res, (err) => { + if (err) { + const [message, status] = errorMessages[err.type] || ["Invalid Body", 400]; + const errorMessage = message.includes("charset") || message.includes("encoding") ? `${message} "${err.charset || err.encoding}"` : message; + return next(new HTTPError(errorMessage, status)); + } + next(); + }); + }; } diff --git a/src/api/middlewares/CORS.ts b/src/api/middlewares/CORS.ts index 34532d3..2318625 100644 --- a/src/api/middlewares/CORS.ts +++ b/src/api/middlewares/CORS.ts @@ -21,20 +21,20 @@ // TODO: config settings export function CORS(req: Request, res: Response, next: NextFunction) { - res.set("Access-Control-Allow-Credentials", "true"); - res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); - res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*"); - res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*"); - res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically - // TODO: use better CSP - res.set( - "Content-security-policy", - "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';", - ); + res.set("Access-Control-Allow-Credentials", "true"); + res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); + res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*"); + res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*"); + res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically + // TODO: use better CSP + res.set( + "Content-security-policy", + "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';", + ); - if (req.method === "OPTIONS") { - res.status(204).end(); - return; - } - next(); + if (req.method === "OPTIONS") { + res.status(204).end(); + return; + } + next(); } diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts index cd306c9..1a9a18a 100644 --- a/src/api/middlewares/ErrorHandler.ts +++ b/src/api/middlewares/ErrorHandler.ts @@ -22,48 +22,48 @@ const EntityNotFoundErrorRegex = /"(\w+)"/; export function ErrorHandler(error: Error & { type?: string }, req: Request, res: Response, next: NextFunction) { - if (!error) return next(); + if (!error) return next(); - try { - let code = 400; - let httpcode = code; - let message = error?.toString(); - let errors = undefined; - let _ajvErrors = undefined; + try { + let code = 400; + let httpcode = code; + let message = error?.toString(); + let errors = undefined; + let _ajvErrors = undefined; - if (error instanceof HTTPError && error.code) code = httpcode = error.code; - else if (error instanceof ApiError) { - code = error.code; - message = error.message; - httpcode = error.httpStatus; - } else if (error.name === "EntityNotFoundError") { - message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; - code = httpcode = 404; - } else if (error instanceof FieldError) { - code = Number(error.code); - message = error.message; - errors = error.errors; - _ajvErrors = error._ajvErrors; - } else if (error?.type == "entity.parse.failed") { - // body-parser failed - httpcode = 400; - code = 50109; - message = "The request body contains invalid JSON."; - } else { - console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body); + if (error instanceof HTTPError && error.code) code = httpcode = error.code; + else if (error instanceof ApiError) { + code = error.code; + message = error.message; + httpcode = error.httpStatus; + } else if (error.name === "EntityNotFoundError") { + message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; + code = httpcode = 404; + } else if (error instanceof FieldError) { + code = Number(error.code); + message = error.message; + errors = error.errors; + _ajvErrors = error._ajvErrors; + } else if (error?.type == "entity.parse.failed") { + // body-parser failed + httpcode = 400; + code = 50109; + message = "The request body contains invalid JSON."; + } else { + console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body); - if (req.server?.options?.production) { - // don't expose internal errors to the user, instead human errors should be thrown as HTTPError - message = "Internal Server Error"; - } - code = httpcode = 500; - } + if (req.server?.options?.production) { + // don't expose internal errors to the user, instead human errors should be thrown as HTTPError + message = "Internal Server Error"; + } + code = httpcode = 500; + } - if (httpcode > 511) httpcode = 400; + if (httpcode > 511) httpcode = 400; - res.status(httpcode).json({ code: code, message, errors, _ajvErrors }); - } catch (error) { - console.error(`[Internal Server Error] 500`, error); - return res.status(500).json({ code: 500, message: "Internal Server Error" }); - } + res.status(httpcode).json({ code: code, message, errors, _ajvErrors }); + } catch (error) { + console.error(`[Internal Server Error] 500`, error); + return res.status(500).json({ code: 500, message: "Internal Server Error" }); + } } diff --git a/src/api/middlewares/ImageProxy.ts b/src/api/middlewares/ImageProxy.ts index 7869012..e950ea8 100644 --- a/src/api/middlewares/ImageProxy.ts +++ b/src/api/middlewares/ImageProxy.ts @@ -25,9 +25,9 @@ let Jimp: JimpType | undefined = undefined; try { - Jimp = require("jimp") as JimpType; + Jimp = require("jimp") as JimpType; } catch { - // empty + // empty } let sentImageProxyWarning = false; @@ -37,101 +37,101 @@ const resizeSupported = new Set([...sharpSupported, ...jimpSupported]); export async function ImageProxy(req: Request, res: Response) { - const path = req.originalUrl.split("/").slice(2); + const path = req.originalUrl.split("/").slice(2); - // src/api/util/utility/EmbedHandlers.ts getProxyUrl - const hash = crypto.createHmac("sha1", Config.get().security.requestSignature).update(path.slice(1).join("/")).digest("base64").replace(/\+/g, "-").replace(/\//g, "_"); + // src/api/util/utility/EmbedHandlers.ts getProxyUrl + const hash = crypto.createHmac("sha1", Config.get().security.requestSignature).update(path.slice(1).join("/")).digest("base64").replace(/\+/g, "-").replace(/\//g, "_"); - try { - if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) throw new Error("Invalid signature"); - } catch { - console.log("[ImageProxy] Invalid signature, expected " + hash + " but got " + path[0]); - res.status(403).send("Invalid signature"); - return; - } + try { + if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) throw new Error("Invalid signature"); + } catch { + console.log("[ImageProxy] Invalid signature, expected " + hash + " but got " + path[0]); + res.status(403).send("Invalid signature"); + return; + } - const abort = new AbortController(); - setTimeout(() => abort.abort(), 5000); + const abort = new AbortController(); + setTimeout(() => abort.abort(), 5000); - const request = await fetch("https://" + path.slice(2).join("/"), { - headers: { - "User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)", - }, - signal: abort.signal, - }).catch((e) => { - if (e.name === "AbortError") res.status(504).send("Request timed out"); - else res.status(500).send("Unable to proxy origin: " + e.message); - }); - if (!request) return; + const request = await fetch("https://" + path.slice(2).join("/"), { + headers: { + "User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)", + }, + signal: abort.signal, + }).catch((e) => { + if (e.name === "AbortError") res.status(504).send("Request timed out"); + else res.status(500).send("Unable to proxy origin: " + e.message); + }); + if (!request) return; - if (request.status !== 200) { - res.status(request.status).send("Origin failed to respond: " + request.status + " " + request.statusText); - return; - } + if (request.status !== 200) { + res.status(request.status).send("Origin failed to respond: " + request.status + " " + request.statusText); + return; + } - if (!request.headers.get("Content-Type") || !request.headers.get("Content-Length")) { - res.status(500).send("Origin did not provide a Content-Type or Content-Length header"); - return; - } + if (!request.headers.get("Content-Type") || !request.headers.get("Content-Length")) { + res.status(500).send("Origin did not provide a Content-Type or Content-Length header"); + return; + } - // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above) - if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) { - res.status(500).send("Origin provided a Content-Length header that is too large"); - return; - } + // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above) + if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) { + res.status(500).send("Origin provided a Content-Length header that is too large"); + return; + } - // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above) - let contentType: string = request.headers.get("Content-Type"); + // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above) + let contentType: string = request.headers.get("Content-Type"); - const arrayBuffer = await request.arrayBuffer(); - let resultBuffer = Buffer.from(arrayBuffer); + const arrayBuffer = await request.arrayBuffer(); + let resultBuffer = Buffer.from(arrayBuffer); - if (!sentImageProxyWarning && resizeSupported.has(contentType) && /^\d+x\d+$/.test(path[1])) { - if (sharp !== false) { - try { - sharp = await import("sharp"); - } catch { - sharp = false; - } - } + if (!sentImageProxyWarning && resizeSupported.has(contentType) && /^\d+x\d+$/.test(path[1])) { + if (sharp !== false) { + try { + sharp = await import("sharp"); + } catch { + sharp = false; + } + } - if (sharp === false && !Jimp) { - try { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore Typings don't fit - Jimp = await import("jimp"); - } catch { - sentImageProxyWarning = true; - console.log(`[ImageProxy] ${yellow('Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled')}`); - } - } + if (sharp === false && !Jimp) { + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Typings don't fit + Jimp = await import("jimp"); + } catch { + sentImageProxyWarning = true; + console.log(`[ImageProxy] ${yellow('Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled')}`); + } + } - const [width, height] = path[1].split("x").map((x) => parseInt(x)); + const [width, height] = path[1].split("x").map((x) => parseInt(x)); - const buffer = Buffer.from(arrayBuffer); - if (sharp && sharpSupported.has(contentType)) { - resultBuffer = Buffer.from( - await sharp - .default(buffer) - // Sharp doesn't support "scaleToFit" - .resize(width) - .toBuffer(), - ); - } else if (Jimp && jimpSupported.has(contentType)) { - resultBuffer = await Jimp.read(buffer).then((image) => { - contentType = image.getMIME(); - return ( - image - .scaleToFit(width, height) - // @ts-expect-error Jimp is defined at this point - .getBufferAsync(Jimp.AUTO) - ); - }); - } - } + const buffer = Buffer.from(arrayBuffer); + if (sharp && sharpSupported.has(contentType)) { + resultBuffer = Buffer.from( + await sharp + .default(buffer) + // Sharp doesn't support "scaleToFit" + .resize(width) + .toBuffer(), + ); + } else if (Jimp && jimpSupported.has(contentType)) { + resultBuffer = await Jimp.read(buffer).then((image) => { + contentType = image.getMIME(); + return ( + image + .scaleToFit(width, height) + // @ts-expect-error Jimp is defined at this point + .getBufferAsync(Jimp.AUTO) + ); + }); + } + } - res.header("Content-Type", contentType); - res.setHeader("Cache-Control", "public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds); + res.header("Content-Type", contentType); + res.setHeader("Cache-Control", "public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds); - res.send(resultBuffer); + res.send(resultBuffer); } diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts index 1a41ad4..c749df6 100644 --- a/src/api/middlewares/RateLimit.ts +++ b/src/api/middlewares/RateLimit.ts @@ -34,188 +34,188 @@ */ type RateLimit = { - id: "global" | "error" | string; - executor_id: string; - hits: number; - blocked: boolean; - expires_at: Date; + id: "global" | "error" | string; + executor_id: string; + hits: number; + blocked: boolean; + expires_at: Date; }; const Cache = new Map(); const EventRateLimit = "RATELIMIT"; export default function rateLimit(opts: { - bucket?: string; - window: number; - count: number; - bot?: number; - webhook?: number; - oauth?: number; - GET?: number; - MODIFY?: number; - error?: boolean; - success?: boolean; - onlyIp?: boolean; + bucket?: string; + window: number; + count: number; + bot?: number; + webhook?: number; + oauth?: number; + GET?: number; + MODIFY?: number; + error?: boolean; + success?: boolean; + onlyIp?: boolean; }) { - return async (req: Request, res: Response, next: NextFunction) => { - // exempt user? if so, immediately short circuit - if (req.user_id) { - const rights = await getRights(req.user_id); - if (rights.has("BYPASS_RATE_LIMITS")) return next(); - } + return async (req: Request, res: Response, next: NextFunction) => { + // exempt user? if so, immediately short circuit + if (req.user_id) { + const rights = await getRights(req.user_id); + if (rights.has("BYPASS_RATE_LIMITS")) return next(); + } - const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); - let executor_id = req.ip || "127.0.0.1"; - if (!opts.onlyIp && req.user_id) executor_id = req.user_id; + const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); + let executor_id = req.ip || "127.0.0.1"; + if (!opts.onlyIp && req.user_id) executor_id = req.user_id; - let max_hits = opts.count; - if (opts.bot && req.user_bot) max_hits = opts.bot; - if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; - else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; + let max_hits = opts.count; + if (opts.bot && req.user_bot) max_hits = opts.bot; + if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; + else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; - const offender = Cache.get(executor_id + bucket_id); + const offender = Cache.get(executor_id + bucket_id); - res.set("X-RateLimit-Limit", `${max_hits}`) - .set("X-RateLimit-Remaining", `${max_hits - (offender?.hits || 0)}`) - .set("X-RateLimit-Bucket", `${bucket_id}`) - // assuming we aren't blocked, a new window will start after this request - .set("X-RateLimit-Reset", `${Date.now() + opts.window}`) - .set("X-RateLimit-Reset-After", `${opts.window}`); + res.set("X-RateLimit-Limit", `${max_hits}`) + .set("X-RateLimit-Remaining", `${max_hits - (offender?.hits || 0)}`) + .set("X-RateLimit-Bucket", `${bucket_id}`) + // assuming we aren't blocked, a new window will start after this request + .set("X-RateLimit-Reset", `${Date.now() + opts.window}`) + .set("X-RateLimit-Reset-After", `${opts.window}`); - if (offender) { - let reset = offender.expires_at.getTime(); - let resetAfterMs = reset - Date.now(); - let resetAfterSec = Math.ceil(resetAfterMs / 1000); + if (offender) { + let reset = offender.expires_at.getTime(); + let resetAfterMs = reset - Date.now(); + let resetAfterSec = Math.ceil(resetAfterMs / 1000); - if (resetAfterMs <= 0) { - offender.hits = 0; - offender.expires_at = new Date(Date.now() + opts.window * 1000); - offender.blocked = false; + if (resetAfterMs <= 0) { + offender.hits = 0; + offender.expires_at = new Date(Date.now() + opts.window * 1000); + offender.blocked = false; - Cache.delete(executor_id + bucket_id); - } + Cache.delete(executor_id + bucket_id); + } - res.set("X-RateLimit-Reset", `${reset}`); - res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`); + res.set("X-RateLimit-Reset", `${reset}`); + res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`); - if (offender.blocked) { - const global = bucket_id === "global"; - // each block violation pushes the expiry one full window further - reset += opts.window * 1000; - offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000); - resetAfterMs = reset - Date.now(); - resetAfterSec = Math.ceil(resetAfterMs / 1000); + if (offender.blocked) { + const global = bucket_id === "global"; + // each block violation pushes the expiry one full window further + reset += opts.window * 1000; + offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000); + resetAfterMs = reset - Date.now(); + resetAfterSec = Math.ceil(resetAfterMs / 1000); - console.log(`blocked bucket: ${bucket_id} ${executor_id}`, { - resetAfterMs, - }); + console.log(`blocked bucket: ${bucket_id} ${executor_id}`, { + resetAfterMs, + }); - if (global) res.set("X-RateLimit-Global", "true"); + if (global) res.set("X-RateLimit-Global", "true"); - return ( - res - .status(429) - .set("X-RateLimit-Remaining", "0") - .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`) - // TODO: error rate limit message translation - .send({ - message: "You are being rate limited.", - retry_after: resetAfterSec, - global, - }) - ); - } - } + return ( + res + .status(429) + .set("X-RateLimit-Remaining", "0") + .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`) + // TODO: error rate limit message translation + .send({ + message: "You are being rate limited.", + retry_after: resetAfterSec, + global, + }) + ); + } + } - next(); - const hitRouteOpts = { - bucket_id, - executor_id, - max_hits, - window: opts.window, - }; + next(); + const hitRouteOpts = { + bucket_id, + executor_id, + max_hits, + window: opts.window, + }; - if (opts.error || opts.success) { - res.once("finish", () => { - // check if error and increment error rate limit - if (res.statusCode >= 400 && opts.error) { - return hitRoute(hitRouteOpts); - } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) { - return hitRoute(hitRouteOpts); - } - }); - } else { - return hitRoute(hitRouteOpts); - } - }; + if (opts.error || opts.success) { + res.once("finish", () => { + // check if error and increment error rate limit + if (res.statusCode >= 400 && opts.error) { + return hitRoute(hitRouteOpts); + } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) { + return hitRoute(hitRouteOpts); + } + }); + } else { + return hitRoute(hitRouteOpts); + } + }; } export async function initRateLimits(app: Router) { - const { routes, global, ip, error, enabled } = Config.get().limits.rate; - if (!enabled) return; - console.log("Enabling rate limits..."); - await listenEvent(EventRateLimit, (event) => { - Cache.set(event.channel_id as string, event.data); - event.acknowledge?.(); - }); - // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date - // const limits = await RateLimit.find({ blocked: true }); - // limits.forEach((limit) => { - // Cache.set(limit.executor_id, limit); - // }); + const { routes, global, ip, error, enabled } = Config.get().limits.rate; + if (!enabled) return; + console.log("Enabling rate limits..."); + await listenEvent(EventRateLimit, (event) => { + Cache.set(event.channel_id as string, event.data); + event.acknowledge?.(); + }); + // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date + // const limits = await RateLimit.find({ blocked: true }); + // limits.forEach((limit) => { + // Cache.set(limit.executor_id, limit); + // }); - setInterval(() => { - Cache.forEach((x, key) => { - if (new Date() > x.expires_at) { - Cache.delete(key); - // RateLimit.delete({ executor_id: key }); - } - }); - }, 1000 * 60); + setInterval(() => { + Cache.forEach((x, key) => { + if (new Date() > x.expires_at) { + Cache.delete(key); + // RateLimit.delete({ executor_id: key }); + } + }); + }, 1000 * 60); - app.use( - rateLimit({ - bucket: "global", - onlyIp: true, - ...ip, - }), - ); - app.use(rateLimit({ bucket: "global", ...global })); - app.use( - rateLimit({ - bucket: "error", - error: true, - onlyIp: true, - ...error, - }), - ); - app.use("/guilds/:guild_id", rateLimit(routes.guild)); - app.use("/webhooks/:webhook_id", rateLimit(routes.webhook)); - app.use("/channels/:channel_id", rateLimit(routes.channel)); - app.use("/auth/login", rateLimit(routes.auth.login)); - app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); + app.use( + rateLimit({ + bucket: "global", + onlyIp: true, + ...ip, + }), + ); + app.use(rateLimit({ bucket: "global", ...global })); + app.use( + rateLimit({ + bucket: "error", + error: true, + onlyIp: true, + ...error, + }), + ); + app.use("/guilds/:guild_id", rateLimit(routes.guild)); + app.use("/webhooks/:webhook_id", rateLimit(routes.webhook)); + app.use("/channels/:channel_id", rateLimit(routes.channel)); + app.use("/auth/login", rateLimit(routes.auth.login)); + app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { - const id = opts.executor_id + opts.bucket_id; - let limit = Cache.get(id); - if (!limit) { - limit = { - id: opts.bucket_id, - executor_id: opts.executor_id, - expires_at: new Date(Date.now() + opts.window * 1000), - hits: 0, - blocked: false, - }; - Cache.set(id, limit); - } + const id = opts.executor_id + opts.bucket_id; + let limit = Cache.get(id); + if (!limit) { + limit = { + id: opts.bucket_id, + executor_id: opts.executor_id, + expires_at: new Date(Date.now() + opts.window * 1000), + hits: 0, + blocked: false, + }; + Cache.set(id, limit); + } - limit.hits++; - if (limit.hits >= opts.max_hits) { - limit.blocked = true; - } + limit.hits++; + if (limit.hits >= opts.max_hits) { + limit.blocked = true; + } - /* + /* let ratelimit = await RateLimit.findOne({ where: { id: opts.bucket_id, executor_id: opts.executor_id } }); if (!ratelimit) { ratelimit = new RateLimit({ diff --git a/src/api/middlewares/Translation.ts b/src/api/middlewares/Translation.ts index e089407..3ca204b 100644 --- a/src/api/middlewares/Translation.ts +++ b/src/api/middlewares/Translation.ts @@ -26,23 +26,23 @@ const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets"); export async function initTranslation(router: Router) { - const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales")); - const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en")); - const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5)); + const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales")); + const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en")); + const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5)); - await i18next - .use(i18nextBackend) - .use(i18nextMiddleware.LanguageDetector) - .init({ - preload: languages, - // debug: true, - fallbackLng: "en", - ns, - backend: { - loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json", - }, - load: "all", - }); + await i18next + .use(i18nextBackend) + .use(i18nextMiddleware.LanguageDetector) + .init({ + preload: languages, + // debug: true, + fallbackLng: "en", + ns, + backend: { + loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json", + }, + load: "all", + }); - router.use(i18nextMiddleware.handle(i18next, {})); + router.use(i18nextMiddleware.handle(i18next, {})); } diff --git a/src/api/routes/-/healthz.ts b/src/api/routes/-/healthz.ts index 5acd8c8..886473c 100644 --- a/src/api/routes/-/healthz.ts +++ b/src/api/routes/-/healthz.ts @@ -23,9 +23,9 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - if (!getDatabase()) return res.sendStatus(503); + if (!getDatabase()) return res.sendStatus(503); - return res.sendStatus(200); + return res.sendStatus(200); }); export default router; diff --git a/src/api/routes/-/readyz.ts b/src/api/routes/-/readyz.ts index 5acd8c8..886473c 100644 --- a/src/api/routes/-/readyz.ts +++ b/src/api/routes/-/readyz.ts @@ -23,9 +23,9 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - if (!getDatabase()) return res.sendStatus(503); + if (!getDatabase()) return res.sendStatus(503); - return res.sendStatus(200); + return res.sendStatus(200); }); export default router; diff --git "a/src/api/routes/applications/\043application_id/bot/index.ts" "b/src/api/routes/applications/\043application_id/bot/index.ts" index ad49184..83b4fc6 100644 --- "a/src/api/routes/applications/\043application_id/bot/index.ts" +++ "b/src/api/routes/applications/\043application_id/bot/index.ts" @@ -26,98 +26,98 @@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - responses: { - 204: { - body: "TokenOnlyResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["owner"], - }); + "/", + route({ + responses: { + 204: { + body: "TokenOnlyResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["owner"], + }); - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - const user = await createAppBotUser(app, req); + const user = await createAppBotUser(app, req); - res.send({ - token: await generateToken(user.id), - }); - }, + res.send({ + token: await generateToken(user.id), + }); + }, ); router.post( - "/reset", - route({ - responses: { - 200: { - body: "TokenResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const bot = await User.findOneOrFail({ where: { id: req.params.application_id } }); - const owner = await User.findOneOrFail({ where: { id: req.user_id } }); + "/reset", + route({ + responses: { + 200: { + body: "TokenResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const bot = await User.findOneOrFail({ where: { id: req.params.application_id } }); + const owner = await User.findOneOrFail({ where: { id: req.user_id } }); - if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - bot.data = { hash: undefined, valid_tokens_since: new Date() }; + bot.data = { hash: undefined, valid_tokens_since: new Date() }; - await bot.save(); + await bot.save(); - const token = await generateToken(bot.id); + const token = await generateToken(bot.id); - res.json({ token }).status(200); - }, + res.json({ token }).status(200); + }, ); router.patch( - "/", - route({ - requestBody: "BotModifySchema", - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as BotModifySchema; - if (!body.avatar?.trim()) delete body.avatar; + "/", + route({ + requestBody: "BotModifySchema", + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as BotModifySchema; + if (!body.avatar?.trim()) delete body.avatar; - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["bot", "owner"], - }); + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["bot", "owner"], + }); - if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; + if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string); + if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string); - app.bot.assign(body); + app.bot.assign(body); - app.bot.save(); + app.bot.save(); - await app.save(); - res.json(app).status(200); - }, + await app.save(); + res.json(app).status(200); + }, ); export default router; diff --git "a/src/api/routes/applications/\043application_id/commands/\043command_id/index.ts" "b/src/api/routes/applications/\043application_id/commands/\043command_id/index.ts" index 7907c1b..8e4c3ce 100644 --- "a/src/api/routes/applications/\043application_id/commands/\043command_id/index.ts" +++ "b/src/api/routes/applications/\043application_id/commands/\043command_id/index.ts" @@ -24,99 +24,99 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id } }); + const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id } }); - if (!command) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!command) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - res.send(command); + res.send(command); }); router.patch( - "/", - route({ - requestBody: "ApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "ApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } }); - if (!commandExists) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!commandExists) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema; + const body = req.body as ApplicationCommandCreateSchema; - if (!body.type) { - body.type = 1; - } + if (!body.type) { + body.type = 1; + } - if (body.name.trim().length < 1 || body.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (body.name.trim().length < 1 || body.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - name: body.name.trim(), - name_localizations: body.name_localizations, - description: body.description?.trim() || "", - description_localizations: body.description_localizations, - default_member_permissions: body.default_member_permissions || null, - contexts: body.contexts, - dm_permission: body.dm_permission || true, - global_popularity_rank: 1, - handler: body.handler, - integration_types: body.integration_types, - nsfw: body.nsfw, - options: body.options, - type: body.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + name: body.name.trim(), + name_localizations: body.name_localizations, + description: body.description?.trim() || "", + description_localizations: body.description_localizations, + default_member_permissions: body.default_member_permissions || null, + contexts: body.contexts, + dm_permission: body.dm_permission || true, + global_popularity_rank: 1, + handler: body.handler, + integration_types: body.integration_types, + nsfw: body.nsfw, + options: body.options, + type: body.type, + version: Snowflake.generate(), + }; - await ApplicationCommand.update({ name: body.name.trim() }, commandForDb); - res.send(commandForDb); - }, + await ApplicationCommand.update({ name: body.name.trim() }, commandForDb); + res.send(commandForDb); + }, ); router.delete("/", async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } }); - if (!commandExists) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!commandExists) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - await ApplicationCommand.delete({ application_id: req.params.application_id, id: req.params.command_id }); - res.sendStatus(204); + await ApplicationCommand.delete({ application_id: req.params.application_id, id: req.params.command_id }); + res.sendStatus(204); }); export default router; diff --git "a/src/api/routes/applications/\043application_id/commands/index.ts" "b/src/api/routes/applications/\043application_id/commands/index.ts" index 97753f0..91824be 100644 --- "a/src/api/routes/applications/\043application_id/commands/index.ts" +++ "b/src/api/routes/applications/\043application_id/commands/index.ts" @@ -25,147 +25,147 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id } }); - res.send(command); + const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id } }); + res.send(command); }); router.post( - "/", - route({ - requestBody: "ApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "ApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema; + const body = req.body as ApplicationCommandCreateSchema; - if (!body.type) { - body.type = 1; - } + if (!body.type) { + body.type = 1; + } - if (body.name.trim().length < 1 || body.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (body.name.trim().length < 1 || body.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - name: body.name.trim(), - name_localizations: body.name_localizations, - description: body.description?.trim() || "", - description_localizations: body.description_localizations, - default_member_permissions: body.default_member_permissions || null, - contexts: body.contexts, - dm_permission: body.dm_permission || true, - global_popularity_rank: 1, - handler: body.handler, - integration_types: body.integration_types, - nsfw: body.nsfw, - options: body.options, - type: body.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + name: body.name.trim(), + name_localizations: body.name_localizations, + description: body.description?.trim() || "", + description_localizations: body.description_localizations, + default_member_permissions: body.default_member_permissions || null, + contexts: body.contexts, + dm_permission: body.dm_permission || true, + global_popularity_rank: 1, + handler: body.handler, + integration_types: body.integration_types, + nsfw: body.nsfw, + options: body.options, + type: body.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: body.name.trim() } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: body.name.trim() } }); - if (commandExists) { - await ApplicationCommand.update({ application_id: req.params.application_id, name: body.name.trim() }, commandForDb); - } else { - commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change - await ApplicationCommand.save(commandForDb); - } + if (commandExists) { + await ApplicationCommand.update({ application_id: req.params.application_id, name: body.name.trim() }, commandForDb); + } else { + commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change + await ApplicationCommand.save(commandForDb); + } - res.send(body); - }, + res.send(body); + }, ); router.put( - "/", - route({ - requestBody: "BulkApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "BulkApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema[]; + const body = req.body as ApplicationCommandCreateSchema[]; - // Remove commands not present in array - const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: IsNull() } }); + // Remove commands not present in array + const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: IsNull() } }); - const commandNamesInArray = body.map((c) => c.name); - const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name)); + const commandNamesInArray = body.map((c) => c.name); + const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name)); - for (const command of commandsNotInArray) { - await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: IsNull(), id: command.id }); - } + for (const command of commandsNotInArray) { + await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: IsNull(), id: command.id }); + } - for (const command of body) { - if (!command.type) { - command.type = 1; - } + for (const command of body) { + if (!command.type) { + command.type = 1; + } - if (command.name.trim().length < 1 || command.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (command.name.trim().length < 1 || command.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - name: command.name.trim(), - name_localizations: command.name_localizations, - description: command.description?.trim() || "", - description_localizations: command.description_localizations, - default_member_permissions: command.default_member_permissions || null, - contexts: command.contexts, - dm_permission: command.dm_permission || true, - global_popularity_rank: 1, - handler: command.handler, - integration_types: command.integration_types, - nsfw: command.nsfw, - options: command.options, - type: command.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + name: command.name.trim(), + name_localizations: command.name_localizations, + description: command.description?.trim() || "", + description_localizations: command.description_localizations, + default_member_permissions: command.default_member_permissions || null, + contexts: command.contexts, + dm_permission: command.dm_permission || true, + global_popularity_rank: 1, + handler: command.handler, + integration_types: command.integration_types, + nsfw: command.nsfw, + options: command.options, + type: command.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: command.name.trim() } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: command.name.trim() } }); - if (commandExists) { - await ApplicationCommand.update({ application_id: req.params.application_id, name: command.name.trim() }, commandForDb); - } else { - commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change - await ApplicationCommand.save(commandForDb); - } - } + if (commandExists) { + await ApplicationCommand.update({ application_id: req.params.application_id, name: command.name.trim() }, commandForDb); + } else { + commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change + await ApplicationCommand.save(commandForDb); + } + } - res.send(body); - }, + res.send(body); + }, ); export default router; diff --git "a/src/api/routes/applications/\043application_id/entitlements.ts" "b/src/api/routes/applications/\043application_id/entitlements.ts" index 63a7e7b..4cd13b9 100644 --- "a/src/api/routes/applications/\043application_id/entitlements.ts" +++ "b/src/api/routes/applications/\043application_id/entitlements.ts" @@ -22,19 +22,19 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "ApplicationEntitlementsResponse", - }, - }, - }), - (req: Request, res: Response) => { - // TODO: - //const { exclude_consumed } = req.query; - res.status(200).send([]); - }, + "/", + route({ + responses: { + 200: { + body: "ApplicationEntitlementsResponse", + }, + }, + }), + (req: Request, res: Response) => { + // TODO: + //const { exclude_consumed } = req.query; + res.status(200).send([]); + }, ); export default router; diff --git "a/src/api/routes/applications/\043application_id/guilds/\043guild_id/commands/\043command_id/index.ts" "b/src/api/routes/applications/\043application_id/guilds/\043guild_id/commands/\043command_id/index.ts" index 2570358..be2d62e 100644 --- "a/src/api/routes/applications/\043application_id/guilds/\043guild_id/commands/\043command_id/index.ts" +++ "b/src/api/routes/applications/\043application_id/guilds/\043guild_id/commands/\043command_id/index.ts" @@ -24,140 +24,140 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id, guild_id: req.params.guild_id } }); + const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id, guild_id: req.params.guild_id } }); - if (!command) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!command) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - res.send(command); + res.send(command); }); router.patch( - "/", - route({ - requestBody: "ApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "ApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema; + const body = req.body as ApplicationCommandCreateSchema; - if (!body.type) { - body.type = 1; - } + if (!body.type) { + body.type = 1; + } - if (body.name.trim().length < 1 || body.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (body.name.trim().length < 1 || body.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - name: body.name.trim(), - name_localizations: body.name_localizations, - description: body.description?.trim() || "", - description_localizations: body.description_localizations, - default_member_permissions: body.default_member_permissions || null, - contexts: body.contexts, - dm_permission: body.dm_permission || true, - global_popularity_rank: 1, - handler: body.handler, - integration_types: body.integration_types, - nsfw: body.nsfw, - options: body.options, - type: body.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + name: body.name.trim(), + name_localizations: body.name_localizations, + description: body.description?.trim() || "", + description_localizations: body.description_localizations, + default_member_permissions: body.default_member_permissions || null, + contexts: body.contexts, + dm_permission: body.dm_permission || true, + global_popularity_rank: 1, + handler: body.handler, + integration_types: body.integration_types, + nsfw: body.nsfw, + options: body.options, + type: body.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ - where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() }, - }); + const commandExists = await ApplicationCommand.exists({ + where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() }, + }); - if (!commandExists) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!commandExists) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - await ApplicationCommand.update( - { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() }, - commandForDb, - ); - res.send(commandForDb); - }, + await ApplicationCommand.update( + { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() }, + commandForDb, + ); + res.send(commandForDb); + }, ); router.delete("/", async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id } }); - if (!commandExists) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!commandExists) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id }); - res.sendStatus(204); + await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id }); + res.sendStatus(204); }); export default router; diff --git "a/src/api/routes/applications/\043application_id/guilds/\043guild_id/commands/index.ts" "b/src/api/routes/applications/\043application_id/guilds/\043guild_id/commands/index.ts" index 1639e7b..607c846 100644 --- "a/src/api/routes/applications/\043application_id/guilds/\043guild_id/commands/index.ts" +++ "b/src/api/routes/applications/\043application_id/guilds/\043guild_id/commands/index.ts" @@ -24,185 +24,185 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } }); - res.send(command); + const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } }); + res.send(command); }); router.post( - "/", - route({ - requestBody: "ApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "ApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema; + const body = req.body as ApplicationCommandCreateSchema; - if (!body.type) { - body.type = 1; - } + if (!body.type) { + body.type = 1; + } - if (body.name.trim().length < 1 || body.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (body.name.trim().length < 1 || body.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - guild_id: req.params.guild_id, - name: body.name.trim(), - name_localizations: body.name_localizations, - description: body.description?.trim() || "", - description_localizations: body.description_localizations, - default_member_permissions: body.default_member_permissions || null, - contexts: body.contexts, - dm_permission: body.dm_permission || true, - global_popularity_rank: 1, - handler: body.handler, - integration_types: body.integration_types, - nsfw: body.nsfw, - options: body.options, - type: body.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + guild_id: req.params.guild_id, + name: body.name.trim(), + name_localizations: body.name_localizations, + description: body.description?.trim() || "", + description_localizations: body.description_localizations, + default_member_permissions: body.default_member_permissions || null, + contexts: body.contexts, + dm_permission: body.dm_permission || true, + global_popularity_rank: 1, + handler: body.handler, + integration_types: body.integration_types, + nsfw: body.nsfw, + options: body.options, + type: body.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() } }); - if (commandExists) { - await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() }, commandForDb); - } else { - commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change - await ApplicationCommand.save(commandForDb); - } + if (commandExists) { + await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() }, commandForDb); + } else { + commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change + await ApplicationCommand.save(commandForDb); + } - res.send(body); - }, + res.send(body); + }, ); router.put( - "/", - route({ - requestBody: "BulkApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "BulkApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema[]; + const body = req.body as ApplicationCommandCreateSchema[]; - // Remove commands not present in array - const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } }); + // Remove commands not present in array + const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } }); - const commandNamesInArray = body.map((c) => c.name); - const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name)); + const commandNamesInArray = body.map((c) => c.name); + const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name)); - for (const command of commandsNotInArray) { - await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: command.id }); - } + for (const command of commandsNotInArray) { + await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: command.id }); + } - for (const command of body) { - if (!command.type) { - command.type = 1; - } + for (const command of body) { + if (!command.type) { + command.type = 1; + } - if (command.name.trim().length < 1 || command.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (command.name.trim().length < 1 || command.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - guild_id: req.params.guild_id, - name: command.name.trim(), - name_localizations: command.name_localizations, - description: command.description?.trim() || "", - description_localizations: command.description_localizations, - default_member_permissions: command.default_member_permissions || null, - contexts: command.contexts, - dm_permission: command.dm_permission || true, - global_popularity_rank: 1, - handler: command.handler, - integration_types: command.integration_types, - nsfw: command.nsfw, - options: command.options, - type: command.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + guild_id: req.params.guild_id, + name: command.name.trim(), + name_localizations: command.name_localizations, + description: command.description?.trim() || "", + description_localizations: command.description_localizations, + default_member_permissions: command.default_member_permissions || null, + contexts: command.contexts, + dm_permission: command.dm_permission || true, + global_popularity_rank: 1, + handler: command.handler, + integration_types: command.integration_types, + nsfw: command.nsfw, + options: command.options, + type: command.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name } }); - if (commandExists) { - await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name }, commandForDb); - } else { - commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change - await ApplicationCommand.save(commandForDb); - } - } + if (commandExists) { + await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name }, commandForDb); + } else { + commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change + await ApplicationCommand.save(commandForDb); + } + } - res.send(body); - }, + res.send(body); + }, ); export default router; diff --git "a/src/api/routes/applications/\043application_id/index.ts" "b/src/api/routes/applications/\043application_id/index.ts" index c38895d..bf29f40 100644 --- "a/src/api/routes/applications/\043application_id/index.ts" +++ "b/src/api/routes/applications/\043application_id/index.ts" @@ -26,106 +26,106 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["owner", "bot"], - }); - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + "/", + route({ + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["owner", "bot"], + }); + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - return res.json(app); - }, + return res.json(app); + }, ); router.patch( - "/", - route({ - requestBody: "ApplicationModifySchema", - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as ApplicationModifySchema; + "/", + route({ + requestBody: "ApplicationModifySchema", + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationModifySchema; - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["owner", "bot"], - }); + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["owner", "bot"], + }); - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - if (body.icon) { - body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string); - } - if (body.cover_image) { - body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string); - } + if (body.icon) { + body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string); + } + if (body.cover_image) { + body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string); + } - if (body.guild_id) { - const guild = await Guild.findOneOrFail({ - where: { id: body.guild_id }, - select: ["owner_id"], - }); - if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400); - } + if (body.guild_id) { + const guild = await Guild.findOneOrFail({ + where: { id: body.guild_id }, + select: ["owner_id"], + }); + if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400); + } - if (app.bot) { - app.bot.assign({ bio: body.description }); - await app.bot.save(); - } + if (app.bot) { + app.bot.assign({ bio: body.description }); + await app.bot.save(); + } - app.assign(body); + app.assign(body); - await app.save(); + await app.save(); - return res.json(app); - }, + return res.json(app); + }, ); router.post( - "/delete", - route({ - responses: { - 200: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["bot", "owner"], - }); - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + "/delete", + route({ + responses: { + 200: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["bot", "owner"], + }); + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - if (app.bot) { - await User.delete({ id: app.id }); - } - await Application.delete({ id: app.id }); + if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if (app.bot) { + await User.delete({ id: app.id }); + } + await Application.delete({ id: app.id }); - res.send().status(200); - }, + res.send().status(200); + }, ); export default router; diff --git "a/src/api/routes/applications/\043application_id/skus.ts" "b/src/api/routes/applications/\043application_id/skus.ts" index 0877f4f..9b9e338 100644 --- "a/src/api/routes/applications/\043application_id/skus.ts" +++ "b/src/api/routes/applications/\043application_id/skus.ts" @@ -22,17 +22,17 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "ApplicationSkusResponse", - }, - }, - }), - async (req: Request, res: Response) => { - res.json([]).status(200); - }, + "/", + route({ + responses: { + 200: { + body: "ApplicationSkusResponse", + }, + }, + }), + async (req: Request, res: Response) => { + res.json([]).status(200); + }, ); export default router; diff --git a/src/api/routes/applications/@me.ts b/src/api/routes/applications/@me.ts index f4a9097..c1cf0db 100644 --- a/src/api/routes/applications/@me.ts +++ b/src/api/routes/applications/@me.ts @@ -27,74 +27,74 @@ // TODO: actually make this be correct - this is just a copy paste of /applications/:id/index.ts minus delete router.get( - "/", - route({ - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.user_id }, - relations: ["owner", "bot"], - }); + "/", + route({ + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.user_id }, + relations: ["owner", "bot"], + }); - return res.json(app); - }, + return res.json(app); + }, ); router.patch( - "/", - route({ - requestBody: "ApplicationModifySchema", - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as ApplicationModifySchema; + "/", + route({ + requestBody: "ApplicationModifySchema", + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationModifySchema; - const app = await Application.findOneOrFail({ - where: { id: req.user_id }, - relations: ["owner", "bot"], - }); + const app = await Application.findOneOrFail({ + where: { id: req.user_id }, + relations: ["owner", "bot"], + }); - if (body.icon) { - body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string); - } - if (body.cover_image) { - body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string); - } + if (body.icon) { + body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string); + } + if (body.cover_image) { + body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string); + } - if (body.guild_id) { - const guild = await Guild.findOneOrFail({ - where: { id: body.guild_id }, - select: ["owner_id"], - }); - if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400); - } + if (body.guild_id) { + const guild = await Guild.findOneOrFail({ + where: { id: body.guild_id }, + select: ["owner_id"], + }); + if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400); + } - if (app.bot) { - app.bot.assign({ bio: body.description }); - await app.bot.save(); - } + if (app.bot) { + app.bot.assign({ bio: body.description }); + await app.bot.save(); + } - app.assign(body); + app.assign(body); - await app.save(); + await app.save(); - return res.json(app); - }, + return res.json(app); + }, ); export default router; diff --git a/src/api/routes/applications/detectable.ts b/src/api/routes/applications/detectable.ts index 4de8a78..83122e7 100644 --- a/src/api/routes/applications/detectable.ts +++ b/src/api/routes/applications/detectable.ts @@ -22,32 +22,32 @@ const router: Router = Router({ mergeParams: true }); const cache = { - data: {}, - expires: 0, + data: {}, + expires: 0, }; router.get( - "/", - route({ - responses: { - 200: { - body: "ApplicationDetectableResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // cache for 6 hours - if (Date.now() > cache.expires) { - const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways - const data = await response.json(); - cache.data = data as ApplicationDetectableResponse; - cache.expires = Date.now() + 6 * 60 * 60 * 1000; - } + "/", + route({ + responses: { + 200: { + body: "ApplicationDetectableResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // cache for 6 hours + if (Date.now() > cache.expires) { + const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways + const data = await response.json(); + cache.data = data as ApplicationDetectableResponse; + cache.expires = Date.now() + 6 * 60 * 60 * 1000; + } - res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`) - .status(200) - .json(cache.data); - }, + res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`) + .status(200) + .json(cache.data); + }, ); export default router; diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts index e3b270c..ff89f03 100644 --- a/src/api/routes/applications/index.ts +++ b/src/api/routes/applications/index.ts @@ -24,54 +24,54 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIApplicationArray", - }, - }, - }), - async (req: Request, res: Response) => { - const results = await Application.find({ - where: { owner: { id: req.user_id } }, - relations: ["owner", "bot"], - }); - res.json(results).status(200); - }, + "/", + route({ + responses: { + 200: { + body: "APIApplicationArray", + }, + }, + }), + async (req: Request, res: Response) => { + const results = await Application.find({ + where: { owner: { id: req.user_id } }, + relations: ["owner", "bot"], + }); + res.json(results).status(200); + }, ); router.post( - "/", - route({ - requestBody: "ApplicationCreateSchema", - responses: { - 200: { - body: "Application", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as ApplicationCreateSchema; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); + "/", + route({ + requestBody: "ApplicationCreateSchema", + responses: { + 200: { + body: "Application", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationCreateSchema; + const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const app = Application.create({ - name: trimSpecial(body.name), - description: "", - bot_public: true, - owner: user, - verify_key: "IMPLEMENTME", - flags: 0, - }); + const app = Application.create({ + name: trimSpecial(body.name), + description: "", + bot_public: true, + owner: user, + verify_key: "IMPLEMENTME", + flags: 0, + }); - // april 14, 2023: discord made bot users be automatically added to all new apps - const { autoCreateBotUsers } = Config.get().general; - if (autoCreateBotUsers) { - await createAppBotUser(app, req); - } else await app.save(); + // april 14, 2023: discord made bot users be automatically added to all new apps + const { autoCreateBotUsers } = Config.get().general; + if (autoCreateBotUsers) { + await createAppBotUser(app, req); + } else await app.save(); - res.json(app); - }, + res.json(app); + }, ); export default router; diff --git a/src/api/routes/attachments/refresh-urls.ts b/src/api/routes/attachments/refresh-urls.ts index 92a7dd1..f153091 100644 --- a/src/api/routes/attachments/refresh-urls.ts +++ b/src/api/routes/attachments/refresh-urls.ts @@ -23,37 +23,37 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "RefreshUrlsRequestSchema", - responses: { - 200: { - body: "RefreshUrlsResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { attachment_urls } = req.body as RefreshUrlsRequestSchema; + "/", + route({ + requestBody: "RefreshUrlsRequestSchema", + responses: { + 200: { + body: "RefreshUrlsResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { attachment_urls } = req.body as RefreshUrlsRequestSchema; - const refreshed_urls = attachment_urls.map((url) => { - return getUrlSignature( - new NewUrlSignatureData({ - url: url, - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ) - .applyToUrl(url) - .toString(); - }); + const refreshed_urls = attachment_urls.map((url) => { + return getUrlSignature( + new NewUrlSignatureData({ + url: url, + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ) + .applyToUrl(url) + .toString(); + }); - return res.status(200).json({ - refreshed_urls, - }); - }, + return res.status(200).json({ + refreshed_urls, + }); + }, ); export default router; diff --git a/src/api/routes/auth/fingerprint.ts b/src/api/routes/auth/fingerprint.ts index d7ef34b..a4a49d0 100644 --- a/src/api/routes/auth/fingerprint.ts +++ b/src/api/routes/auth/fingerprint.ts @@ -21,9 +21,9 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.post("/", route({ responses: { 200: { body: "CreateFingerprintResponse" } } }), (req: Request, res: Response) => { - const snowflake = Snowflake.generate(); - return res.json({ - fingerprint: `${snowflake}.${createHash("sha512").update(snowflake).digest("base64")}`, - }); + const snowflake = Snowflake.generate(); + return res.json({ + fingerprint: `${snowflake}.${createHash("sha512").update(snowflake).digest("base64")}`, + }); }); export default router; diff --git a/src/api/routes/auth/forgot.ts b/src/api/routes/auth/forgot.ts index 6e61382..db16c3b 100644 --- a/src/api/routes/auth/forgot.ts +++ b/src/api/routes/auth/forgot.ts @@ -23,55 +23,55 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "ForgotPasswordSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorOrCaptchaResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { login, captcha_key } = req.body as ForgotPasswordSchema; + "/", + route({ + requestBody: "ForgotPasswordSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorOrCaptchaResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { login, captcha_key } = req.body as ForgotPasswordSchema; - const config = Config.get(); + const config = Config.get(); - if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) { - const { sitekey, service } = config.security.captcha; - if (!captcha_key) { - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } + if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) { + const { sitekey, service } = config.security.captcha; + if (!captcha_key) { + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } - const ip = req.ip; - const verify = await verifyCaptcha(captcha_key, ip); - if (!verify.success) { - return res.status(400).json({ - captcha_key: verify["error-codes"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } - } + const ip = req.ip; + const verify = await verifyCaptcha(captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } + } - res.sendStatus(204); + res.sendStatus(204); - const user = await User.findOne({ - where: [{ phone: login }, { email: login }], - select: ["username", "id", "email"], - }).catch(() => {}); + const user = await User.findOne({ + where: [{ phone: login }, { email: login }], + select: ["username", "id", "email"], + }).catch(() => {}); - if (user && user.email) { - Email.sendResetPassword(user, user.email).catch((e) => { - console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`); - }); - } - }, + if (user && user.email) { + Email.sendResetPassword(user, user.email).catch((e) => { + console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`); + }); + } + }, ); export default router; diff --git a/src/api/routes/auth/generate-registration-tokens.ts b/src/api/routes/auth/generate-registration-tokens.ts index be93295..a6b70c6 100644 --- a/src/api/routes/auth/generate-registration-tokens.ts +++ b/src/api/routes/auth/generate-registration-tokens.ts @@ -24,46 +24,46 @@ export default router; router.get( - "/", - route({ - query: { - count: { - type: "number", - description: "The number of registration tokens to generate. Defaults to 1.", - }, - length: { - type: "number", - description: "The length of each registration token. Defaults to 255.", - }, - }, - right: "CREATE_REGISTRATION_TOKENS", - responses: { 200: { body: "GenerateRegistrationTokensResponse" } }, - }), - async (req: Request, res: Response) => { - const count = req.query.count ? parseInt(req.query.count as string) : 1; - const length = req.query.length ? parseInt(req.query.length as string) : 255; + "/", + route({ + query: { + count: { + type: "number", + description: "The number of registration tokens to generate. Defaults to 1.", + }, + length: { + type: "number", + description: "The length of each registration token. Defaults to 255.", + }, + }, + right: "CREATE_REGISTRATION_TOKENS", + responses: { 200: { body: "GenerateRegistrationTokensResponse" } }, + }), + async (req: Request, res: Response) => { + const count = req.query.count ? parseInt(req.query.count as string) : 1; + const length = req.query.length ? parseInt(req.query.length as string) : 255; - const tokens: ValidRegistrationToken[] = []; + const tokens: ValidRegistrationToken[] = []; - for (let i = 0; i < count; i++) { - const token = ValidRegistrationToken.create({ - token: randomString(length), - expires_at: new Date(Date.now() + Config.get().security.defaultRegistrationTokenExpiration), - }); - tokens.push(token); - } + for (let i = 0; i < count; i++) { + const token = ValidRegistrationToken.create({ + token: randomString(length), + expires_at: new Date(Date.now() + Config.get().security.defaultRegistrationTokenExpiration), + }); + tokens.push(token); + } - // Why are these options used, exactly? - await ValidRegistrationToken.save(tokens, { - chunk: 1000, - reload: false, - transaction: false, - }); + // Why are these options used, exactly? + await ValidRegistrationToken.save(tokens, { + chunk: 1000, + reload: false, + transaction: false, + }); - const ret = req.query.include_url ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`) : tokens.map((x) => x.token); + const ret = req.query.include_url ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`) : tokens.map((x) => x.token); - if (req.query.plain) return res.send(ret.join("\n")); + if (req.query.plain) return res.send(ret.join("\n")); - return res.json({ tokens: ret }); - }, + return res.json({ tokens: ret }); + }, ); diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts index 92f4790..6f2f26a 100644 --- a/src/api/routes/auth/location-metadata.ts +++ b/src/api/routes/auth/location-metadata.ts @@ -22,24 +22,24 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "LocationMetadataResponse", - }, - }, - }), - async (req: Request, res: Response) => { - //TODO - //Note: It's most likely related to legal. At the moment Discord hasn't finished this too - const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code; - res.json({ - consent_required: false, - country_code: country_code, - promotional_email_opt_in: { required: true, pre_checked: false }, - }); - }, + "/", + route({ + responses: { + 200: { + body: "LocationMetadataResponse", + }, + }, + }), + async (req: Request, res: Response) => { + //TODO + //Note: It's most likely related to legal. At the moment Discord hasn't finished this too + const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code; + res.json({ + consent_required: false, + country_code: country_code, + promotional_email_opt_in: { required: true, pre_checked: false }, + }); + }, ); export default router; diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts index c8ef627..911ad4a 100644 --- a/src/api/routes/auth/login.ts +++ b/src/api/routes/auth/login.ts @@ -27,157 +27,157 @@ export default router; router.post( - "/", - route({ - requestBody: "LoginSchema", - responses: { - 200: { - body: "LoginResponse", - }, - 400: { - body: "APIErrorOrCaptchaResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { login, password, captcha_key, undelete } = req.body as LoginSchema; + "/", + route({ + requestBody: "LoginSchema", + responses: { + 200: { + body: "LoginResponse", + }, + 400: { + body: "APIErrorOrCaptchaResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { login, password, captcha_key, undelete } = req.body as LoginSchema; - const config = Config.get(); + const config = Config.get(); - if (config.login.requireCaptcha && config.security.captcha.enabled) { - const { sitekey, service } = config.security.captcha; - if (!captcha_key) { - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } + if (config.login.requireCaptcha && config.security.captcha.enabled) { + const { sitekey, service } = config.security.captcha; + if (!captcha_key) { + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } - const ip = req.ip; - const verify = await verifyCaptcha(captcha_key, ip); - if (!verify.success) { - return res.status(400).json({ - captcha_key: verify["error-codes"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } - } + const ip = req.ip; + const verify = await verifyCaptcha(captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } + } - const user = await User.findOneOrFail({ - where: [{ phone: login }, { email: login }], - select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"], - relations: ["security_keys", "settings"], - }).catch(() => { - throw FieldErrors({ - login: { - message: req.t("auth:login.INVALID_LOGIN"), - code: "INVALID_LOGIN", - }, - password: { - message: req.t("auth:login.INVALID_LOGIN"), - code: "INVALID_LOGIN", - }, - }); - }); + const user = await User.findOneOrFail({ + where: [{ phone: login }, { email: login }], + select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"], + relations: ["security_keys", "settings"], + }).catch(() => { + throw FieldErrors({ + login: { + message: req.t("auth:login.INVALID_LOGIN"), + code: "INVALID_LOGIN", + }, + password: { + message: req.t("auth:login.INVALID_LOGIN"), + code: "INVALID_LOGIN", + }, + }); + }); - // the salt is saved in the password refer to bcrypt docs - const same_password = await bcrypt.compare(password, user.data.hash || ""); - if (!same_password) { - throw FieldErrors({ - login: { - message: req.t("auth:login.INVALID_LOGIN"), - code: "INVALID_LOGIN", - }, - password: { - message: req.t("auth:login.INVALID_LOGIN"), - code: "INVALID_LOGIN", - }, - }); - } + // the salt is saved in the password refer to bcrypt docs + const same_password = await bcrypt.compare(password, user.data.hash || ""); + if (!same_password) { + throw FieldErrors({ + login: { + message: req.t("auth:login.INVALID_LOGIN"), + code: "INVALID_LOGIN", + }, + password: { + message: req.t("auth:login.INVALID_LOGIN"), + code: "INVALID_LOGIN", + }, + }); + } - // return an error for unverified accounts if verification is required - if (config.login.requireVerification && !user.verified) { - throw FieldErrors({ - login: { - code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL", - message: "Email verification is required, please check your email.", - }, - }); - } + // return an error for unverified accounts if verification is required + if (config.login.requireVerification && !user.verified) { + throw FieldErrors({ + login: { + code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL", + message: "Email verification is required, please check your email.", + }, + }); + } - if (user.mfa_enabled && !user.webauthn_enabled) { - // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy - const ticket = crypto.randomBytes(40).toString("hex"); + if (user.mfa_enabled && !user.webauthn_enabled) { + // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy + const ticket = crypto.randomBytes(40).toString("hex"); - await User.update({ id: user.id }, { totp_last_ticket: ticket }); + await User.update({ id: user.id }, { totp_last_ticket: ticket }); - return res.json({ - ticket: ticket, - mfa: true, - sms: false, // TODO - token: null, - }); - } + return res.json({ + ticket: ticket, + mfa: true, + sms: false, // TODO + token: null, + }); + } - if (user.mfa_enabled && user.webauthn_enabled) { - if (!WebAuthn.fido2) { - // TODO: I did this for typescript and I can't use ! - throw new Error("WebAuthn not enabled"); - } + if (user.mfa_enabled && user.webauthn_enabled) { + if (!WebAuthn.fido2) { + // TODO: I did this for typescript and I can't use ! + throw new Error("WebAuthn not enabled"); + } - const options = await WebAuthn.fido2.assertionOptions(); - const challenge = JSON.stringify({ - publicKey: { - ...options, - challenge: Buffer.from(options.challenge).toString("base64"), - allowCredentials: user.security_keys.map((x) => ({ - id: x.key_id, - type: "public-key", - })), - transports: ["usb", "ble", "nfc"], - timeout: 60000, - }, - }); + const options = await WebAuthn.fido2.assertionOptions(); + const challenge = JSON.stringify({ + publicKey: { + ...options, + challenge: Buffer.from(options.challenge).toString("base64"), + allowCredentials: user.security_keys.map((x) => ({ + id: x.key_id, + type: "public-key", + })), + transports: ["usb", "ble", "nfc"], + timeout: 60000, + }, + }); - const ticket = await generateWebAuthnTicket(challenge); - await User.update({ id: user.id }, { totp_last_ticket: ticket }); + const ticket = await generateWebAuthnTicket(challenge); + await User.update({ id: user.id }, { totp_last_ticket: ticket }); - return res.json({ - ticket: ticket, - mfa: true, - sms: false, // TODO - token: null, - webauthn: challenge, - }); - } + return res.json({ + ticket: ticket, + mfa: true, + sms: false, // TODO + token: null, + webauthn: challenge, + }); + } - if (undelete) { - // undelete refers to un'disable' here - if (user.disabled) await User.update({ id: user.id }, { disabled: false }); - if (user.deleted) await User.update({ id: user.id }, { deleted: false }); - } else { - if (user.deleted) - return res.status(400).json({ - message: "This account is scheduled for deletion.", - code: 20011, - }); - if (user.disabled) - return res.status(400).json({ - message: req.t("auth:login.ACCOUNT_DISABLED"), - code: 20013, - }); - } + if (undelete) { + // undelete refers to un'disable' here + if (user.disabled) await User.update({ id: user.id }, { disabled: false }); + if (user.deleted) await User.update({ id: user.id }, { deleted: false }); + } else { + if (user.deleted) + return res.status(400).json({ + message: "This account is scheduled for deletion.", + code: 20011, + }); + if (user.disabled) + return res.status(400).json({ + message: req.t("auth:login.ACCOUNT_DISABLED"), + code: 20013, + }); + } - const token = await generateToken(user.id); + const token = await generateToken(user.id); - // Notice this will have a different token structure, than discord - // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package - // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png + // Notice this will have a different token structure, than discord + // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package + // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png - res.json({ token, settings: { ...user.settings, index: undefined } }); - }, + res.json({ token, settings: { ...user.settings, index: undefined } }); + }, ); /** diff --git a/src/api/routes/auth/logout.ts b/src/api/routes/auth/logout.ts index f20a199..b460e31 100644 --- a/src/api/routes/auth/logout.ts +++ b/src/api/routes/auth/logout.ts @@ -24,25 +24,25 @@ export default router; router.post( - "/", - route({ - responses: { - 204: {}, - }, - }), - async (req: Request, res: Response) => { - if (req.body.provider != null || req.body.voip_provider != null) { - console.log(`[LOGOUT]: provider or voip provider not null!`, req.body); - } else { - delete req.body.provider; - delete req.body.voip_provider; - if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body); - } + "/", + route({ + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + if (req.body.provider != null || req.body.voip_provider != null) { + console.log(`[LOGOUT]: provider or voip provider not null!`, req.body); + } else { + delete req.body.provider; + delete req.body.voip_provider; + if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body); + } - if (req.token.did) { - await Session.delete({ user_id: req.user_id, session_id: req.token.did }); - } + if (req.token.did) { + await Session.delete({ user_id: req.user_id, session_id: req.token.did }); + } - res.status(204).send(); - }, + res.status(204).send(); + }, ); diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts index edf307c..b9f2fa8 100644 --- a/src/api/routes/auth/mfa/totp.ts +++ b/src/api/routes/auth/mfa/totp.ts @@ -25,54 +25,54 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "TotpSchema", - responses: { - 200: { - body: "TokenResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // const { code, ticket, gift_code_sku_id, login_source } = - const { code, ticket } = req.body as TotpSchema; + "/", + route({ + requestBody: "TotpSchema", + responses: { + 200: { + body: "TokenResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // const { code, ticket, gift_code_sku_id, login_source } = + const { code, ticket } = req.body as TotpSchema; - const user = await User.findOneOrFail({ - where: { - totp_last_ticket: ticket, - }, - select: ["id", "totp_secret"], - relations: ["settings"], - }); + const user = await User.findOneOrFail({ + where: { + totp_last_ticket: ticket, + }, + select: ["id", "totp_secret"], + relations: ["settings"], + }); - const backup = await BackupCode.findOne({ - where: { - code: code, - expired: false, - consumed: false, - user: { id: user.id }, - }, - }); + const backup = await BackupCode.findOne({ + where: { + code: code, + expired: false, + consumed: false, + user: { id: user.id }, + }, + }); - if (!backup) { - const ret = verifyToken(user.totp_secret || "", code); - if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - } else { - backup.consumed = true; - await backup.save(); - } + if (!backup) { + const ret = verifyToken(user.totp_secret || "", code); + if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + } else { + backup.consumed = true; + await backup.save(); + } - await User.update({ id: user.id }, { totp_last_ticket: "" }); + await User.update({ id: user.id }, { totp_last_ticket: "" }); - return res.json({ - token: await generateToken(user.id), - settings: { ...user.settings, index: undefined }, - }); - }, + return res.json({ + token: await generateToken(user.id), + settings: { ...user.settings, index: undefined }, + }); + }, ); export default router; diff --git a/src/api/routes/auth/mfa/webauthn.ts b/src/api/routes/auth/mfa/webauthn.ts index 63b1335..da476ab 100644 --- a/src/api/routes/auth/mfa/webauthn.ts +++ b/src/api/routes/auth/mfa/webauthn.ts @@ -25,77 +25,77 @@ const router = Router({ mergeParams: true }); function toArrayBuffer(buf: Buffer) { - const ab = new ArrayBuffer(buf.length); - const view = new Uint8Array(ab); - for (let i = 0; i < buf.length; ++i) { - view[i] = buf[i]; - } - return ab; + const ab = new ArrayBuffer(buf.length); + const view = new Uint8Array(ab); + for (let i = 0; i < buf.length; ++i) { + view[i] = buf[i]; + } + return ab; } router.post( - "/", - route({ - requestBody: "WebAuthnTotpSchema", - responses: { - 200: { body: "TokenResponse" }, - 400: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - if (!WebAuthn.fido2) { - // TODO: I did this for typescript and I can't use ! - throw new Error("WebAuthn not enabled"); - } + "/", + route({ + requestBody: "WebAuthnTotpSchema", + responses: { + 200: { body: "TokenResponse" }, + 400: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + if (!WebAuthn.fido2) { + // TODO: I did this for typescript and I can't use ! + throw new Error("WebAuthn not enabled"); + } - const { code, ticket } = req.body as WebAuthnTotpSchema; + const { code, ticket } = req.body as WebAuthnTotpSchema; - const user = await User.findOneOrFail({ - where: { - totp_last_ticket: ticket, - }, - select: ["id"], - relations: ["settings"], - }); + const user = await User.findOneOrFail({ + where: { + totp_last_ticket: ticket, + }, + select: ["id"], + relations: ["settings"], + }); - const ret = await verifyWebAuthnToken(ticket); - if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + const ret = await verifyWebAuthnToken(ticket); + if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - await User.update({ id: user.id }, { totp_last_ticket: "" }); + await User.update({ id: user.id }, { totp_last_ticket: "" }); - const clientAttestationResponse = JSON.parse(code); + const clientAttestationResponse = JSON.parse(code); - if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400); + if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400); - clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url")); + clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url")); - const securityKey = await SecurityKey.findOneOrFail({ - where: { - key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"), - }, - }); + const securityKey = await SecurityKey.findOneOrFail({ + where: { + key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"), + }, + }); - const assertionExpectations: ExpectedAssertionResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString()); + const assertionExpectations: ExpectedAssertionResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString()); - const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, { - ...assertionExpectations, - factor: "second", - publicKey: securityKey.public_key, - prevCounter: securityKey.counter, - userHandle: securityKey.key_id, - }); + const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, { + ...assertionExpectations, + factor: "second", + publicKey: securityKey.public_key, + prevCounter: securityKey.counter, + userHandle: securityKey.key_id, + }); - const counter = authnResult.authnrData.get("counter"); + const counter = authnResult.authnrData.get("counter"); - securityKey.counter = counter; + securityKey.counter = counter; - await securityKey.save(); + await securityKey.save(); - return res.json({ - token: await generateToken(user.id), - user_settings: user.settings, - }); - }, + return res.json({ + token: await generateToken(user.id), + user_settings: user.settings, + }); + }, ); export default router; diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts index d4b061b..6aa52bf 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts @@ -27,294 +27,294 @@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "RegisterSchema", - responses: { - 200: { body: "TokenOnlyResponse" }, - 400: { body: "APIErrorOrCaptchaResponse" }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as RegisterSchema; - const { register, security, limits } = Config.get(); - const ip = req.ip!; + "/", + route({ + requestBody: "RegisterSchema", + responses: { + 200: { body: "TokenOnlyResponse" }, + 400: { body: "APIErrorOrCaptchaResponse" }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as RegisterSchema; + const { register, security, limits } = Config.get(); + const ip = req.ip!; - // Reg tokens - // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc ) - let regTokenUsed = false; - if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { - // eg theyre on https://staging.spacebar.chat/register?token=whatever - const token = req.get("Referrer")?.split("token=")[1].split("&")[0]; - if (token) { - const regToken = await ValidRegistrationToken.findOneOrFail({ - where: { token, expires_at: MoreThan(new Date()) }, - }); - await regToken.remove(); - regTokenUsed = true; - console.log(`[REGISTER] Registration token ${token} used for registration!`); - } else { - console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`); - } - } + // Reg tokens + // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc ) + let regTokenUsed = false; + if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { + // eg theyre on https://staging.spacebar.chat/register?token=whatever + const token = req.get("Referrer")?.split("token=")[1].split("&")[0]; + if (token) { + const regToken = await ValidRegistrationToken.findOneOrFail({ + where: { token, expires_at: MoreThan(new Date()) }, + }); + await regToken.remove(); + regTokenUsed = true; + console.log(`[REGISTER] Registration token ${token} used for registration!`); + } else { + console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`); + } + } - // check if registration is allowed - if (!regTokenUsed && !register.allowNewRegistration) { - throw FieldErrors({ - email: { - code: "REGISTRATION_DISABLED", - message: req.t("auth:register.REGISTRATION_DISABLED"), - }, - }); - } + // check if registration is allowed + if (!regTokenUsed && !register.allowNewRegistration) { + throw FieldErrors({ + email: { + code: "REGISTRATION_DISABLED", + message: req.t("auth:register.REGISTRATION_DISABLED"), + }, + }); + } - // check if the user agreed to the Terms of Service - if (!body.consent) { - throw FieldErrors({ - consent: { - code: "CONSENT_REQUIRED", - message: req.t("auth:register.CONSENT_REQUIRED"), - }, - }); - } + // check if the user agreed to the Terms of Service + if (!body.consent) { + throw FieldErrors({ + consent: { + code: "CONSENT_REQUIRED", + message: req.t("auth:register.CONSENT_REQUIRED"), + }, + }); + } - if (!regTokenUsed && register.disabled) { - throw FieldErrors({ - email: { - code: "DISABLED", - message: "registration is disabled on this instance", - }, - }); - } + if (!regTokenUsed && register.disabled) { + throw FieldErrors({ + email: { + code: "DISABLED", + message: "registration is disabled on this instance", + }, + }); + } - if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) { - const { sitekey, service } = security.captcha; - if (!body.captcha_key) { - return res?.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } + if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) { + const { sitekey, service } = security.captcha; + if (!body.captcha_key) { + return res?.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } - const verify = await verifyCaptcha(body.captcha_key, ip); - if (!verify.success) { - return res.status(400).json({ - captcha_key: verify["error-codes"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } - } + const verify = await verifyCaptcha(body.captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } + } - if (!regTokenUsed && !register.allowMultipleAccounts) { - // TODO: check if fingerprint was eligible generated - const exists = await User.findOne({ - where: { fingerprints: body.fingerprint }, - select: ["id"], - }); + if (!regTokenUsed && !register.allowMultipleAccounts) { + // TODO: check if fingerprint was eligible generated + const exists = await User.findOne({ + where: { fingerprints: body.fingerprint }, + select: ["id"], + }); - if (exists) { - throw FieldErrors({ - email: { - code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), - }, - }); - } - } + if (exists) { + throw FieldErrors({ + email: { + code: "EMAIL_ALREADY_REGISTERED", + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), + }, + }); + } + } - if (!regTokenUsed && register.checkIp) { - const blacklist = await AbuseIpDbClient.getBlacklist(); - if (blacklist) { - const entry = blacklist.data.find((e) => e.ipAddress === ip); + if (!regTokenUsed && register.checkIp) { + const blacklist = await AbuseIpDbClient.getBlacklist(); + if (blacklist) { + const entry = blacklist.data.find((e) => e.ipAddress === ip); - if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { - console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`); - throw new HTTPError("Your IP is blocked from registration"); - } - } + if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { + console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`); + throw new HTTPError("Your IP is blocked from registration"); + } + } - const checkIp = await AbuseIpDbClient.checkIpAddress(ip); - if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { - console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`); - throw new HTTPError("Your IP is blocked from registration"); - } + const checkIp = await AbuseIpDbClient.checkIpAddress(ip); + if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { + console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`); + throw new HTTPError("Your IP is blocked from registration"); + } - const ipData = await IpDataClient.getIpInfo(ip); - if (ipData) { - if (!ipData.threat) { - console.log("Invalid IPData.co response, missing threat field", ipData); - } - const categories = Object.entries(ipData.threat) - .filter(([key, value]) => key.startsWith("is_") && value === true) - .map(([key]) => key.replace("is_", "")); - const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes)); - if (blockedCategories.size > 0) { - console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`); - throw new HTTPError("Your IP is blocked from registration"); - } + const ipData = await IpDataClient.getIpInfo(ip); + if (ipData) { + if (!ipData.threat) { + console.log("Invalid IPData.co response, missing threat field", ipData); + } + const categories = Object.entries(ipData.threat) + .filter(([key, value]) => key.startsWith("is_") && value === true) + .map(([key]) => key.replace("is_", "")); + const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes)); + if (blockedCategories.size > 0) { + console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`); + throw new HTTPError("Your IP is blocked from registration"); + } - if (register.blockAsnTypes.includes(ipData.asn.type)) { - console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`); - throw new HTTPError("Your IP is blocked from registration"); - } + if (register.blockAsnTypes.includes(ipData.asn.type)) { + console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`); + throw new HTTPError("Your IP is blocked from registration"); + } - if (register.blockAsns.includes(ipData.asn.asn)) { - console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`); - throw new HTTPError("Your IP is blocked from registration"); - } + if (register.blockAsns.includes(ipData.asn.asn)) { + console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`); + throw new HTTPError("Your IP is blocked from registration"); + } - if (register.blockProxies && IpDataClient.isProxy(ipData)) { - console.log(`[Register] ${ip} blocked from registration: IPData.co response matched IpDataClient.isProxy() check`); - throw new HTTPError("Your IP is blocked from registration"); - } - } - } + if (register.blockProxies && IpDataClient.isProxy(ipData)) { + console.log(`[Register] ${ip} blocked from registration: IPData.co response matched IpDataClient.isProxy() check`); + throw new HTTPError("Your IP is blocked from registration"); + } + } + } - // TODO: gift_code_sku_id? - // TODO: check password strength + // TODO: gift_code_sku_id? + // TODO: check password strength - const email = body.email; - if (email) { - // replace all dots and chars after +, if its a gmail.com email - if (!email) { - throw FieldErrors({ - email: { - code: "INVALID_EMAIL", - message: req?.t("auth:register.INVALID_EMAIL"), - }, - }); - } + const email = body.email; + if (email) { + // replace all dots and chars after +, if its a gmail.com email + if (!email) { + throw FieldErrors({ + email: { + code: "INVALID_EMAIL", + message: req?.t("auth:register.INVALID_EMAIL"), + }, + }); + } - // check if there is already an account with this email - const exists = await User.findOne({ where: { email: email } }); + // check if there is already an account with this email + const exists = await User.findOne({ where: { email: email } }); - if (exists) { - throw FieldErrors({ - email: { - code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), - }, - }); - } - } else if (register.email.required) { - throw FieldErrors({ - email: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + if (exists) { + throw FieldErrors({ + email: { + code: "EMAIL_ALREADY_REGISTERED", + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), + }, + }); + } + } else if (register.email.required) { + throw FieldErrors({ + email: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - if (register.dateOfBirth.required && !body.date_of_birth) { - throw FieldErrors({ - date_of_birth: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) { - const minimum = new Date(); - minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); + if (register.dateOfBirth.required && !body.date_of_birth) { + throw FieldErrors({ + date_of_birth: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) { + const minimum = new Date(); + minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); - let parsedDob; - try { - parsedDob = new Date(body.date_of_birth as Date); - if (isNaN(parsedDob.getTime())) { - throw new Error("Invalid date"); - } - } catch (e) { - throw FieldErrors({ - date_of_birth: { - code: "DATE_OF_BIRTH_INVALID", - message: req.t("auth:register.DATE_OF_BIRTH_INVALID"), - }, - }); - } + let parsedDob; + try { + parsedDob = new Date(body.date_of_birth as Date); + if (isNaN(parsedDob.getTime())) { + throw new Error("Invalid date"); + } + } catch (e) { + throw FieldErrors({ + date_of_birth: { + code: "DATE_OF_BIRTH_INVALID", + message: req.t("auth:register.DATE_OF_BIRTH_INVALID"), + }, + }); + } - // higher is younger - if (parsedDob > minimum) { - throw FieldErrors({ - date_of_birth: { - code: "DATE_OF_BIRTH_UNDERAGE", - message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { - years: register.dateOfBirth.minimum, - }), - }, - }); - } - } + // higher is younger + if (parsedDob > minimum) { + throw FieldErrors({ + date_of_birth: { + code: "DATE_OF_BIRTH_UNDERAGE", + message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { + years: register.dateOfBirth.minimum, + }), + }, + }); + } + } - if (body.password) { - const min = register.password.minLength ?? 8; + if (body.password) { + const min = register.password.minLength ?? 8; - if (body.password.length < min) { - throw FieldErrors({ - password: { - code: "PASSWORD_REQUIREMENTS_MIN_LENGTH", - message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }), - }, - }); - } - // the salt is saved in the password refer to bcrypt docs - body.password = await bcrypt.hash(body.password, 12); - } else if (register.password.required) { - throw FieldErrors({ - password: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + if (body.password.length < min) { + throw FieldErrors({ + password: { + code: "PASSWORD_REQUIREMENTS_MIN_LENGTH", + message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }), + }, + }); + } + // the salt is saved in the password refer to bcrypt docs + body.password = await bcrypt.hash(body.password, 12); + } else if (register.password.required) { + throw FieldErrors({ + password: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - if (!regTokenUsed && !body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) { - // require invite to register -> e.g. for organizations to send invites to their employees - throw FieldErrors({ - email: { - code: "INVITE_ONLY", - message: req.t("auth:register.INVITE_ONLY"), - }, - }); - } + if (!regTokenUsed && !body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) { + // require invite to register -> e.g. for organizations to send invites to their employees + throw FieldErrors({ + email: { + code: "INVITE_ONLY", + message: req.t("auth:register.INVITE_ONLY"), + }, + }); + } - if ( - !regTokenUsed && - limits.absoluteRate.register.enabled && - (await User.count({ - where: { - created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)), - }, - })) >= limits.absoluteRate.register.limit - ) { - console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`); - throw FieldErrors({ - email: { - code: "TOO_MANY_REGISTRATIONS", - message: req.t("auth:register.TOO_MANY_REGISTRATIONS"), - }, - }); - } + if ( + !regTokenUsed && + limits.absoluteRate.register.enabled && + (await User.count({ + where: { + created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)), + }, + })) >= limits.absoluteRate.register.limit + ) { + console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`); + throw FieldErrors({ + email: { + code: "TOO_MANY_REGISTRATIONS", + message: req.t("auth:register.TOO_MANY_REGISTRATIONS"), + }, + }); + } - const { maxUsername } = Config.get().limits.user; - if (body.username.length > maxUsername) { - throw FieldErrors({ - username: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 2 and ${maxUsername} in length.`, - }, - }); - } + const { maxUsername } = Config.get().limits.user; + if (body.username.length > maxUsername) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 2 and ${maxUsername} in length.`, + }, + }); + } - const user = await User.register({ ...body, req }); + const user = await User.register({ ...body, req }); - if (body.invite) { - // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible) - await Invite.joinGuild(user.id, body.invite); - } + if (body.invite) { + // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible) + await Invite.joinGuild(user.id, body.invite); + } - return res.json({ token: await generateToken(user.id) }); - }, + return res.json({ token: await generateToken(user.id) }); + }, ); export default router; diff --git a/src/api/routes/auth/reset.ts b/src/api/routes/auth/reset.ts index 69f97d2..77e93e8 100644 --- a/src/api/routes/auth/reset.ts +++ b/src/api/routes/auth/reset.ts @@ -26,54 +26,54 @@ // TODO: the response interface also returns settings, but this route doesn't actually return that. router.post( - "/", - route({ - requestBody: "PasswordResetSchema", - responses: { - 200: { - body: "TokenOnlyResponse", - }, - 400: { - body: "APIErrorOrCaptchaResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { password, token } = req.body as PasswordResetSchema; + "/", + route({ + requestBody: "PasswordResetSchema", + responses: { + 200: { + body: "TokenOnlyResponse", + }, + 400: { + body: "APIErrorOrCaptchaResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { password, token } = req.body as PasswordResetSchema; - let user; - try { - const userTokenData = await checkToken(token, { - select: ["email"], - fingerprint: req.fingerprint, - ipAddress: req.ip, - }); - user = userTokenData.user; - } catch { - throw FieldErrors({ - password: { - message: req.t("auth:password_reset.INVALID_TOKEN"), - code: "INVALID_TOKEN", - }, - }); - } + let user; + try { + const userTokenData = await checkToken(token, { + select: ["email"], + fingerprint: req.fingerprint, + ipAddress: req.ip, + }); + user = userTokenData.user; + } catch { + throw FieldErrors({ + password: { + message: req.t("auth:password_reset.INVALID_TOKEN"), + code: "INVALID_TOKEN", + }, + }); + } - // the salt is saved in the password refer to bcrypt docs - const hash = await bcrypt.hash(password, 12); + // the salt is saved in the password refer to bcrypt docs + const hash = await bcrypt.hash(password, 12); - const data = { - data: { - hash, - valid_tokens_since: new Date(), - }, - }; - await User.update({ id: user.id }, data); + const data = { + data: { + hash, + valid_tokens_since: new Date(), + }, + }; + await User.update({ id: user.id }, data); - // come on, the user has to have an email to reset their password in the first place - await Email.sendPasswordChanged(user, user.email!); + // come on, the user has to have an email to reset their password in the first place + await Email.sendPasswordChanged(user, user.email!); - res.json({ token: await generateToken(user.id) }); - }, + res.json({ token: await generateToken(user.id) }); + }, ); export default router; diff --git a/src/api/routes/auth/sessions.ts b/src/api/routes/auth/sessions.ts index 6c721f2..3a9095f 100644 --- a/src/api/routes/auth/sessions.ts +++ b/src/api/routes/auth/sessions.ts @@ -23,54 +23,54 @@ import { In } from "typeorm"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GetSessionsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { extended = false } = req.query; - const sessions = (await Session.find({ where: { user_id: req.user_id, is_admin_session: false } })) as Session[]; + "/", + route({ + responses: { + 200: { + body: "GetSessionsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { extended = false } = req.query; + const sessions = (await Session.find({ where: { user_id: req.user_id, is_admin_session: false } })) as Session[]; - res.json({ - user_sessions: sessions.map((session) => (extended ? session.getExtendedDeviceInfo() : session.getDiscordDeviceInfo())), - }); - }, + res.json({ + user_sessions: sessions.map((session) => (extended ? session.getExtendedDeviceInfo() : session.getDiscordDeviceInfo())), + }); + }, ); router.post( - "/logout", - route({ - requestBody: "SessionsLogoutSchema", - responses: { - 204: {}, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as SessionsLogoutSchema; + "/logout", + route({ + requestBody: "SessionsLogoutSchema", + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as SessionsLogoutSchema; - let sessions: Session[] = []; - if ("session_ids" in body) { - sessions = (await Session.find({ where: { user_id: req.user_id, session_id: In(body.session_ids!) } })) as Session[]; - } + let sessions: Session[] = []; + if ("session_ids" in body) { + sessions = (await Session.find({ where: { user_id: req.user_id, session_id: In(body.session_ids!) } })) as Session[]; + } - if ("session_id_hashes" in body) { - const allSessions = (await Session.find({ where: { user_id: req.user_id } })) as Session[]; - const hashSet = new Set(body.session_id_hashes); - const matchingSessions = allSessions.filter((session) => { - const hash = createHash("sha256").update(session.session_id).digest("hex"); - return hashSet.has(hash); - }); - sessions.push(...matchingSessions); - } + if ("session_id_hashes" in body) { + const allSessions = (await Session.find({ where: { user_id: req.user_id } })) as Session[]; + const hashSet = new Set(body.session_id_hashes); + const matchingSessions = allSessions.filter((session) => { + const hash = createHash("sha256").update(session.session_id).digest("hex"); + return hashSet.has(hash); + }); + sessions.push(...matchingSessions); + } - for (const session of sessions) { - await session.remove(); - } - res.status(204).send(); - }, + for (const session of sessions) { + await session.remove(); + } + res.status(204).send(); + }, ); export default router; diff --git a/src/api/routes/auth/verify/index.ts b/src/api/routes/auth/verify/index.ts index 43fb23a..7c8ca18 100644 --- a/src/api/routes/auth/verify/index.ts +++ b/src/api/routes/auth/verify/index.ts @@ -22,79 +22,79 @@ const router = Router({ mergeParams: true }); async function getToken(user: User) { - const token = await generateToken(user.id); + const token = await generateToken(user.id); - // Notice this will have a different token structure, than discord - // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package - // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png + // Notice this will have a different token structure, than discord + // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package + // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png - return { token }; + return { token }; } // TODO: the response interface also returns settings, but this route doesn't actually return that. router.post( - "/", - route({ - requestBody: "VerifyEmailSchema", - responses: { - 200: { - body: "TokenResponse", - }, - 400: { - body: "APIErrorOrCaptchaResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { captcha_key, token } = req.body; + "/", + route({ + requestBody: "VerifyEmailSchema", + responses: { + 200: { + body: "TokenResponse", + }, + 400: { + body: "APIErrorOrCaptchaResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { captcha_key, token } = req.body; - const config = Config.get(); + const config = Config.get(); - if (config.register.requireCaptcha && config.security.captcha.enabled) { - const { sitekey, service } = config.security.captcha; + if (config.register.requireCaptcha && config.security.captcha.enabled) { + const { sitekey, service } = config.security.captcha; - if (!captcha_key) { - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } + if (!captcha_key) { + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } - const ip = req.ip; - const verify = await verifyCaptcha(captcha_key, ip); - if (!verify.success) { - return res.status(400).json({ - captcha_key: verify["error-codes"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } - } + const ip = req.ip; + const verify = await verifyCaptcha(captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } + } - let user; + let user; - try { - const userTokenData = await checkToken(token, { - fingerprint: req.fingerprint, - ipAddress: req.ip, - }); - user = userTokenData.user; - } catch { - throw FieldErrors({ - token: { - message: req.t("auth:password_reset.INVALID_TOKEN"), - code: "INVALID_TOKEN", - }, - }); - } + try { + const userTokenData = await checkToken(token, { + fingerprint: req.fingerprint, + ipAddress: req.ip, + }); + user = userTokenData.user; + } catch { + throw FieldErrors({ + token: { + message: req.t("auth:password_reset.INVALID_TOKEN"), + code: "INVALID_TOKEN", + }, + }); + } - if (user.verified) return res.json(await getToken(user)); + if (user.verified) return res.json(await getToken(user)); - await User.update({ id: user.id }, { verified: true }); + await User.update({ id: user.id }, { verified: true }); - return res.json(await getToken(user)); - }, + return res.json(await getToken(user)); + }, ); export default router; diff --git a/src/api/routes/auth/verify/resend.ts b/src/api/routes/auth/verify/resend.ts index 4ef1d51..ce6672a 100644 --- a/src/api/routes/auth/verify/resend.ts +++ b/src/api/routes/auth/verify/resend.ts @@ -23,43 +23,43 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - right: "RESEND_VERIFICATION_EMAIL", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 500: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["username", "email", "verified"], - }); + "/", + route({ + right: "RESEND_VERIFICATION_EMAIL", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 500: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["username", "email", "verified"], + }); - if (!user.email) { - // TODO: whats the proper error response for this? - throw new HTTPError("User does not have an email address", 400); - } + if (!user.email) { + // TODO: whats the proper error response for this? + throw new HTTPError("User does not have an email address", 400); + } - if (user.verified) { - throw new HTTPError("Email is already verified", 400); - } + if (user.verified) { + throw new HTTPError("Email is already verified", 400); + } - await Email.sendVerifyEmail(user, user.email) - .then(() => { - return res.sendStatus(204); - }) - .catch((e) => { - console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`); - throw new HTTPError("Failed to send verification email", 500); - }); - }, + await Email.sendVerifyEmail(user, user.email) + .then(() => { + return res.sendStatus(204); + }) + .catch((e) => { + console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`); + throw new HTTPError("Failed to send verification email", 500); + }); + }, ); export default router; diff --git a/src/api/routes/auth/verify/view-backup-codes-challenge.ts b/src/api/routes/auth/verify/view-backup-codes-challenge.ts index 7c15795..d3d40d3 100644 --- a/src/api/routes/auth/verify/view-backup-codes-challenge.ts +++ b/src/api/routes/auth/verify/view-backup-codes-challenge.ts @@ -24,36 +24,36 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "BackupCodesChallengeSchema", - responses: { - 200: { body: "BackupCodesChallengeResponse" }, - 400: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { password } = req.body as BackupCodesChallengeSchema; + "/", + route({ + requestBody: "BackupCodesChallengeSchema", + responses: { + 200: { body: "BackupCodesChallengeResponse" }, + 400: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { password } = req.body as BackupCodesChallengeSchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); - if (!(await bcrypt.compare(password, user.data.hash || ""))) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } + if (!(await bcrypt.compare(password, user.data.hash || ""))) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } - return res.json({ - nonce: "NoncePlaceholder", - regenerate_nonce: "RegenNoncePlaceholder", - }); - }, + return res.json({ + nonce: "NoncePlaceholder", + regenerate_nonce: "RegenNoncePlaceholder", + }); + }, ); export default router; diff --git a/src/api/routes/beaker.ts b/src/api/routes/beaker.ts index 62df41e..4eb2941 100644 --- a/src/api/routes/beaker.ts +++ b/src/api/routes/beaker.ts @@ -23,16 +23,16 @@ // screw off, telemetry requests router.post( - "/", - route({ - responses: { - 204: {}, - }, - }), - (req: Request, res: Response) => { - // TODO: - res.sendStatus(204); - }, + "/", + route({ + responses: { + 204: {}, + }, + }), + (req: Request, res: Response) => { + // TODO: + res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/attachments.ts" "b/src/api/routes/channels/\043channel_id/attachments.ts" index 039bfa2..30fd2c6 100644 --- "a/src/api/routes/channels/\043channel_id/attachments.ts" +++ "b/src/api/routes/channels/\043channel_id/attachments.ts" @@ -25,109 +25,109 @@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "UploadAttachmentRequestSchema", - responses: { - 200: { - body: "UploadAttachmentResponseSchema", - }, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const payload = req.body as UploadAttachmentRequestSchema; - const { channel_id } = req.params; + "/", + route({ + requestBody: "UploadAttachmentRequestSchema", + responses: { + 200: { + body: "UploadAttachmentResponseSchema", + }, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const payload = req.body as UploadAttachmentRequestSchema; + const { channel_id } = req.params; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.ATTACH_FILES)) { - return res.status(403).json({ - code: 403, - message: "Missing Permissions: ATTACH_FILES", - }); - } + if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.ATTACH_FILES)) { + return res.status(403).json({ + code: 403, + message: "Missing Permissions: ATTACH_FILES", + }); + } - const cdnUrl = Config.get().cdn.endpointPublic; - const batchId = `CLOUD_${user.id}_${randomString(128)}`; + const cdnUrl = Config.get().cdn.endpointPublic; + const batchId = `CLOUD_${user.id}_${randomString(128)}`; - // validate IDs - const seenIds: (string | undefined)[] = []; - for (const file of payload.files) { - if (seenIds.includes(file.id)) { - return res.status(400).json({ - code: 400, - message: `Duplicate attachment ID: ${file.id}`, - }); - } - seenIds.push(file.id); - } + // validate IDs + const seenIds: (string | undefined)[] = []; + for (const file of payload.files) { + if (seenIds.includes(file.id)) { + return res.status(400).json({ + code: 400, + message: `Duplicate attachment ID: ${file.id}`, + }); + } + seenIds.push(file.id); + } - const attachments = await Promise.all( - payload.files.map(async (attachment) => { - attachment.filename = attachment.filename.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); - const uploadFilename = `${channel_id}/${batchId}/${attachment.id ?? "0"}/${attachment.filename}`; - const newAttachment = CloudAttachment.create({ - user: user, - channel: channel, - uploadFilename: uploadFilename, - userAttachmentId: attachment.id ?? "0", - userFilename: attachment.filename, - userFileSize: attachment.file_size, - userIsClip: attachment.is_clip, - userOriginalContentType: attachment.original_content_type, - }); - await newAttachment.save(); - return newAttachment; - }), - ); + const attachments = await Promise.all( + payload.files.map(async (attachment) => { + attachment.filename = attachment.filename.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); + const uploadFilename = `${channel_id}/${batchId}/${attachment.id ?? "0"}/${attachment.filename}`; + const newAttachment = CloudAttachment.create({ + user: user, + channel: channel, + uploadFilename: uploadFilename, + userAttachmentId: attachment.id ?? "0", + userFilename: attachment.filename, + userFileSize: attachment.file_size, + userIsClip: attachment.is_clip, + userOriginalContentType: attachment.original_content_type, + }); + await newAttachment.save(); + return newAttachment; + }), + ); - res.send({ - attachments: attachments.map((a) => { - return { - id: a.userAttachmentId, - upload_filename: a.uploadFilename, - upload_url: `${cdnUrl}/attachments/${a.uploadFilename}`, - original_content_type: a.userOriginalContentType, - }; - }), - } as UploadAttachmentResponseSchema); - }, + res.send({ + attachments: attachments.map((a) => { + return { + id: a.userAttachmentId, + upload_filename: a.uploadFilename, + upload_url: `${cdnUrl}/attachments/${a.uploadFilename}`, + original_content_type: a.userOriginalContentType, + }; + }), + } as UploadAttachmentResponseSchema); + }, ); router.delete("/:cloud_attachment_url", async (req: Request, res: Response) => { - const { channel_id, cloud_attachment_url } = req.params; + const { channel_id, cloud_attachment_url } = req.params; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - const att = await CloudAttachment.findOneOrFail({ where: { uploadFilename: decodeURI(cloud_attachment_url) } }); - if (att.userId !== user.id) { - return res.status(403).json({ - code: 403, - message: "You do not own this attachment.", - }); - } + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const att = await CloudAttachment.findOneOrFail({ where: { uploadFilename: decodeURI(cloud_attachment_url) } }); + if (att.userId !== user.id) { + return res.status(403).json({ + code: 403, + message: "You do not own this attachment.", + }); + } - if (att.channelId !== channel.id) { - return res.status(400).json({ - code: 400, - message: "Attachment does not belong to this channel.", - }); - } + if (att.channelId !== channel.id) { + return res.status(400).json({ + code: 400, + message: "Attachment does not belong to this channel.", + }); + } - const response = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${att.uploadFilename}`, { - headers: { - signature: Config.get().security.requestSignature, - }, - method: "DELETE", - }); + const response = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${att.uploadFilename}`, { + headers: { + signature: Config.get().security.requestSignature, + }, + method: "DELETE", + }); - await att.remove(); - return res.status(response.status).send(response.body); + await att.remove(); + return res.status(response.status).send(response.body); }); export default router; diff --git "a/src/api/routes/channels/\043channel_id/directory-entries.ts" "b/src/api/routes/channels/\043channel_id/directory-entries.ts" index c07b1f7..da95050 100644 --- "a/src/api/routes/channels/\043channel_id/directory-entries.ts" +++ "b/src/api/routes/channels/\043channel_id/directory-entries.ts" @@ -22,20 +22,20 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "HubDirectoryEntriesResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - res.json([] as HubDirectoryEntriesResponse); - }, + "/", + route({ + responses: { + 200: { + body: "HubDirectoryEntriesResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + res.json([] as HubDirectoryEntriesResponse); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/greet.ts" "b/src/api/routes/channels/\043channel_id/greet.ts" index b6ce1ee..f78f26a 100644 --- "a/src/api/routes/channels/\043channel_id/greet.ts" +++ "b/src/api/routes/channels/\043channel_id/greet.ts" @@ -25,83 +25,83 @@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "GreetRequestSchema", - permission: "MANAGE_CHANNELS", - responses: { - 200: { - body: "Message", - }, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const payload = req.body as GreetRequestSchema; - const { channel_id } = req.params; + "/", + route({ + requestBody: "GreetRequestSchema", + permission: "MANAGE_CHANNELS", + responses: { + 200: { + body: "Message", + }, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const payload = req.body as GreetRequestSchema; + const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - const targetMessage = await Message.findOneOrFail({ - where: { - id: payload.message_reference?.message_id, - channel_id: payload.message_reference?.channel_id, - guild_id: payload.message_reference?.guild_id, - }, - }); + const targetMessage = await Message.findOneOrFail({ + where: { + id: payload.message_reference?.message_id, + channel_id: payload.message_reference?.channel_id, + guild_id: payload.message_reference?.guild_id, + }, + }); - if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN) - return res.status(400).json({ - code: 400, // TODO: what's the actual error code? - message: "Cannot send greet message referencing this message.", - }); + if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN) + return res.status(400).json({ + code: 400, // TODO: what's the actual error code? + message: "Cannot send greet message referencing this message.", + }); - if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) { - return res.status(403).json({ - code: 403, - message: "Missing Permissions: SEND_MESSAGES", - }); - } + if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) { + return res.status(403).json({ + code: 403, + message: "Missing Permissions: SEND_MESSAGES", + }); + } - const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick - if (specCompliant && payload.sticker_ids.length != 1) - return res.status(400).json({ - code: 400, - message: "Must include exactly one sticker.", - }); + const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick + if (specCompliant && payload.sticker_ids.length != 1) + return res.status(400).json({ + code: 400, + message: "Must include exactly one sticker.", + }); - const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } }); + const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } }); - const randomSticker = stickers[Math.floor(Math.random() * stickers.length)]; + const randomSticker = stickers[Math.floor(Math.random() * stickers.length)]; - const message = Message.create({ - channel_id: channel_id, - author_id: req.user_id, - type: MessageType.REPLY, - message_reference: { ...payload.message_reference, type: 0 }, - referenced_message: targetMessage, - sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [], - }); + const message = Message.create({ + channel_id: channel_id, + author_id: req.user_id, + type: MessageType.REPLY, + message_reference: { ...payload.message_reference, type: 0 }, + referenced_message: targetMessage, + sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [], + }); - channel.last_message_id = message.id; + channel.last_message_id = message.id; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_CREATE", - data: message, - channel_id, - } as MessageCreateEvent), - channel.save(), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_CREATE", + data: message, + channel_id, + } as MessageCreateEvent), + channel.save(), + ]); - res.send(channel); - }, + res.send(channel); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/index.ts" "b/src/api/routes/channels/\043channel_id/index.ts" index 97857da..37909a9 100644 --- "a/src/api/routes/channels/\043channel_id/index.ts" +++ "b/src/api/routes/channels/\043channel_id/index.ts" @@ -26,132 +26,132 @@ // TODO: Get channel router.get( - "/", - route({ - permission: "VIEW_CHANNEL", - responses: { - 200: { - body: "Channel", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; + "/", + route({ + permission: "VIEW_CHANNEL", + responses: { + 200: { + body: "Channel", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel.guild_id) return res.send(channel); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) return res.send(channel); - channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild); - return res.send(channel); - }, + channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild); + return res.send(channel); + }, ); router.delete( - "/", - route({ - permission: "MANAGE_CHANNELS", - responses: { - 200: { - body: "Channel", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; + "/", + route({ + permission: "MANAGE_CHANNELS", + responses: { + 200: { + body: "Channel", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients"], - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); - if (channel.type === ChannelType.DM) { - const recipient = await Recipient.findOneOrFail({ - where: { channel_id: channel_id, user_id: req.user_id }, - }); - recipient.closed = true; - await Promise.all([ - recipient.save(), - emitEvent({ - event: "CHANNEL_DELETE", - data: channel, - user_id: req.user_id, - } as ChannelDeleteEvent), - ]); - } else if (channel.type === ChannelType.GROUP_DM) { - await Channel.removeRecipientFromChannel(channel, req.user_id); - } else { - if (channel.type == ChannelType.GUILD_CATEGORY) { - const channels = await Channel.find({ - where: { parent_id: channel_id }, - }); - for await (const c of channels) { - c.parent_id = null; + if (channel.type === ChannelType.DM) { + const recipient = await Recipient.findOneOrFail({ + where: { channel_id: channel_id, user_id: req.user_id }, + }); + recipient.closed = true; + await Promise.all([ + recipient.save(), + emitEvent({ + event: "CHANNEL_DELETE", + data: channel, + user_id: req.user_id, + } as ChannelDeleteEvent), + ]); + } else if (channel.type === ChannelType.GROUP_DM) { + await Channel.removeRecipientFromChannel(channel, req.user_id); + } else { + if (channel.type == ChannelType.GUILD_CATEGORY) { + const channels = await Channel.find({ + where: { parent_id: channel_id }, + }); + for await (const c of channels) { + c.parent_id = null; - await Promise.all([ - c.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - data: c, - channel_id: c.id, - } as ChannelUpdateEvent), - ]); - } - } + await Promise.all([ + c.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + data: c, + channel_id: c.id, + } as ChannelUpdateEvent), + ]); + } + } - await Promise.all([ - Channel.deleteChannel(channel), - emitEvent({ - event: "CHANNEL_DELETE", - data: channel, - channel_id, - } as ChannelDeleteEvent), - ]); - } + await Promise.all([ + Channel.deleteChannel(channel), + emitEvent({ + event: "CHANNEL_DELETE", + data: channel, + channel_id, + } as ChannelDeleteEvent), + ]); + } - res.send(channel); - }, + res.send(channel); + }, ); router.patch( - "/", - route({ - requestBody: "ChannelModifySchema", - permission: "MANAGE_CHANNELS", - responses: { - 200: { - body: "Channel", - }, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const payload = req.body as ChannelModifySchema; - const { channel_id } = req.params; - if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon); + "/", + route({ + requestBody: "ChannelModifySchema", + permission: "MANAGE_CHANNELS", + responses: { + 200: { + body: "Channel", + }, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const payload = req.body as ChannelModifySchema; + const { channel_id } = req.params; + if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - channel.assign(payload); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + channel.assign(payload); - await Promise.all([ - channel.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - data: channel, - channel_id, - } as ChannelUpdateEvent), - ]); + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id, + } as ChannelUpdateEvent), + ]); - res.send(channel); - }, + res.send(channel); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/invites.ts" "b/src/api/routes/channels/\043channel_id/invites.ts" index 298f42e..808f0f8 100644 --- "a/src/api/routes/channels/\043channel_id/invites.ts" +++ "b/src/api/routes/channels/\043channel_id/invites.ts" @@ -25,96 +25,96 @@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "InviteCreateSchema", - permission: "CREATE_INSTANT_INVITE", - right: "CREATE_INVITES", - responses: { - 201: { - body: "Invite", - }, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req; - const body = req.body as InviteCreateSchema; - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - select: ["id", "name", "type", "guild_id"], - }); - isTextChannel(channel.type); + "/", + route({ + requestBody: "InviteCreateSchema", + permission: "CREATE_INSTANT_INVITE", + right: "CREATE_INVITES", + responses: { + 201: { + body: "Invite", + }, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req; + const body = req.body as InviteCreateSchema; + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + select: ["id", "name", "type", "guild_id"], + }); + isTextChannel(channel.type); - if (!channel.guild_id) { - throw new HTTPError("This channel doesn't exist", 404); - } - const { guild_id } = channel; + if (!channel.guild_id) { + throw new HTTPError("This channel doesn't exist", 404); + } + const { guild_id } = channel; - const expires_at = body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now()); + const expires_at = body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now()); - const invite = await Invite.create({ - code: randomString(), - temporary: body.temporary || true, - uses: 0, - max_uses: body.max_uses ? Math.max(0, body.max_uses) : 0, - max_age: body.max_age ? Math.max(0, body.max_age) : 0, - expires_at, - created_at: new Date(), - guild_id, - channel_id: channel_id, - inviter_id: user_id, - flags: body.flags ?? 0, - }).save(); + const invite = await Invite.create({ + code: randomString(), + temporary: body.temporary || true, + uses: 0, + max_uses: body.max_uses ? Math.max(0, body.max_uses) : 0, + max_age: body.max_age ? Math.max(0, body.max_age) : 0, + expires_at, + created_at: new Date(), + guild_id, + channel_id: channel_id, + inviter_id: user_id, + flags: body.flags ?? 0, + }).save(); - const data = invite.toJSON(); - data.inviter = (await User.getPublicUser(req.user_id)).toPublicUser(); - data.guild = await Guild.findOne({ where: { id: guild_id } }); - data.channel = channel; + const data = invite.toJSON(); + data.inviter = (await User.getPublicUser(req.user_id)).toPublicUser(); + data.guild = await Guild.findOne({ where: { id: guild_id } }); + data.channel = channel; - await emitEvent({ - event: "INVITE_CREATE", - data, - guild_id, - } as InviteCreateEvent); + await emitEvent({ + event: "INVITE_CREATE", + data, + guild_id, + } as InviteCreateEvent); - res.status(201).send(data); - }, + res.status(201).send(data); + }, ); router.get( - "/", - route({ - permission: "MANAGE_CHANNELS", - responses: { - 200: { - body: "APIInviteArray", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + "/", + route({ + permission: "MANAGE_CHANNELS", + responses: { + 200: { + body: "APIInviteArray", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - if (!channel.guild_id) { - throw new HTTPError("This channel doesn't exist", 404); - } - const { guild_id } = channel; + if (!channel.guild_id) { + throw new HTTPError("This channel doesn't exist", 404); + } + const { guild_id } = channel; - const invites = await Invite.find({ - where: { guild_id, channel_id }, - relations: PublicInviteRelation, - }); + const invites = await Invite.find({ + where: { guild_id, channel_id }, + relations: PublicInviteRelation, + }); - res.status(200).send(invites); - }, + res.status(200).send(invites); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/messages/\043message_id/ack.ts" "b/src/api/routes/channels/\043channel_id/messages/\043message_id/ack.ts" index c7c96fe..7b9f039 100644 --- "a/src/api/routes/channels/\043channel_id/messages/\043message_id/ack.ts" +++ "b/src/api/routes/channels/\043channel_id/messages/\043message_id/ack.ts" @@ -27,42 +27,42 @@ // TODO: advance-only notification cursor router.post( - "/", - route({ - requestBody: "MessageAcknowledgeSchema", - responses: { - 200: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/", + route({ + requestBody: "MessageAcknowledgeSchema", + responses: { + 200: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const permission = await getPermission(req.user_id, undefined, channel_id); - permission.hasThrow("VIEW_CHANNEL"); + const permission = await getPermission(req.user_id, undefined, channel_id); + permission.hasThrow("VIEW_CHANNEL"); - let read_state = await ReadState.findOne({ - where: { user_id: req.user_id, channel_id }, - }); - if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); - read_state.last_message_id = message_id; - //It's a little more complicated but this'll do :P - read_state.mention_count = 0; + let read_state = await ReadState.findOne({ + where: { user_id: req.user_id, channel_id }, + }); + if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); + read_state.last_message_id = message_id; + //It's a little more complicated but this'll do :P + read_state.mention_count = 0; - await read_state.save(); + await read_state.save(); - await emitEvent({ - event: "MESSAGE_ACK", - user_id: req.user_id, - data: { - channel_id, - message_id, - version: 3763, - }, - } as MessageAckEvent); + await emitEvent({ + event: "MESSAGE_ACK", + user_id: req.user_id, + data: { + channel_id, + message_id, + version: 3763, + }, + } as MessageAckEvent); - res.json({ token: null }); - }, + res.json({ token: null }); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/messages/\043message_id/crosspost.ts" "b/src/api/routes/channels/\043channel_id/messages/\043message_id/crosspost.ts" index 6d89c50..2aad157 100644 --- "a/src/api/routes/channels/\043channel_id/messages/\043message_id/crosspost.ts" +++ "b/src/api/routes/channels/\043channel_id/messages/\043message_id/crosspost.ts" @@ -22,43 +22,43 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - permission: "MANAGE_MESSAGES", - responses: { - 200: { - body: "Message", - }, - }, - }), - (req: Request, res: Response) => { - // TODO: - res.json({ - id: "", - type: 0, - content: "", - channel_id: "", - author: { - id: "", - username: "", - avatar: "", - discriminator: "", - public_flags: 64, - }, - attachments: [], - embeds: [], - mentions: [], - mention_roles: [], - pinned: false, - mention_everyone: false, - tts: false, - timestamp: "", - edited_timestamp: null, - flags: 1, - components: [], - poll: {}, - }).status(200); - }, + "/", + route({ + permission: "MANAGE_MESSAGES", + responses: { + 200: { + body: "Message", + }, + }, + }), + (req: Request, res: Response) => { + // TODO: + res.json({ + id: "", + type: 0, + content: "", + channel_id: "", + author: { + id: "", + username: "", + avatar: "", + discriminator: "", + public_flags: 64, + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "", + edited_timestamp: null, + flags: 1, + components: [], + poll: {}, + }).status(200); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/messages/\043message_id/index.ts" "b/src/api/routes/channels/\043channel_id/messages/\043message_id/index.ts" index cdba9b1..64f29c9 100644 --- "a/src/api/routes/channels/\043channel_id/messages/\043message_id/index.ts" +++ "b/src/api/routes/channels/\043channel_id/messages/\043message_id/index.ts" @@ -17,19 +17,19 @@ */ import { - Attachment, - Channel, - Message, - MessageCreateEvent, - MessageDeleteEvent, - MessageUpdateEvent, - Snowflake, - SpacebarApiErrors, - emitEvent, - getPermission, - getRights, - uploadFile, - NewUrlUserSignatureData, + Attachment, + Channel, + Message, + MessageCreateEvent, + MessageDeleteEvent, + MessageUpdateEvent, + Snowflake, + SpacebarApiErrors, + emitEvent, + getPermission, + getRights, + uploadFile, + NewUrlUserSignatureData, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -41,285 +41,285 @@ // TODO: message content/embed string length limit const messageUpload = multer({ - limits: { - fileSize: 1024 * 1024 * 100, - fields: 10, - files: 1, - }, - storage: multer.memoryStorage(), + limits: { + fileSize: 1024 * 1024 * 100, + fields: 10, + files: 1, + }, + storage: multer.memoryStorage(), }); // max upload 50 mb router.patch( - "/", - route({ - requestBody: "MessageEditSchema", - permission: "SEND_MESSAGES", - right: "SEND_MESSAGES", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - let body = req.body as MessageEditSchema; + "/", + route({ + requestBody: "MessageEditSchema", + permission: "SEND_MESSAGES", + right: "SEND_MESSAGES", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + let body = req.body as MessageEditSchema; - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - relations: ["attachments"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + relations: ["attachments"], + }); - const permissions = await getPermission(req.user_id, undefined, channel_id); + const permissions = await getPermission(req.user_id, undefined, channel_id); - const rights = await getRights(req.user_id); + const rights = await getRights(req.user_id); - if (req.user_id !== message.author_id) { - if (!rights.has("MANAGE_MESSAGES")) { - permissions.hasThrow("MANAGE_MESSAGES"); - body = { flags: body.flags }; - // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins - } - } else rights.hasThrow("SELF_EDIT_MESSAGES"); + if (req.user_id !== message.author_id) { + if (!rights.has("MANAGE_MESSAGES")) { + permissions.hasThrow("MANAGE_MESSAGES"); + body = { flags: body.flags }; + // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins + } + } else rights.hasThrow("SELF_EDIT_MESSAGES"); - // no longer necessary, somehow resolved by updating the type of `attachments`...? - // //@ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema - const new_message = await handleMessage({ - ...message, - // TODO: should message_reference be overridable? - message_reference: message.message_reference, - ...body, - author_id: message.author_id, - channel_id, - id: message_id, - edited_timestamp: new Date(), - }); + // no longer necessary, somehow resolved by updating the type of `attachments`...? + // //@ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema + const new_message = await handleMessage({ + ...message, + // TODO: should message_reference be overridable? + message_reference: message.message_reference, + ...body, + author_id: message.author_id, + channel_id, + id: message_id, + edited_timestamp: new Date(), + }); - await Promise.all([ - new_message.save(), - await emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: { - ...new_message.toJSON(), - nonce: undefined, - member: new_message.member?.toPublicMember(), - }, - } as MessageUpdateEvent), - ]); + await Promise.all([ + new_message.save(), + await emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: { + ...new_message.toJSON(), + nonce: undefined, + member: new_message.member?.toPublicMember(), + }, + } as MessageUpdateEvent), + ]); - postHandleMessage(new_message); + postHandleMessage(new_message); - // TODO: a DTO? - return res.json({ - ...new_message.toJSON(), - id: new_message.id, - type: new_message.type, - channel_id: new_message.channel_id, - member: new_message.member?.toPublicMember(), - author: new_message.author?.toPublicUser(), - attachments: new_message.attachments, - embeds: new_message.embeds, - mentions: new_message.embeds, - mention_roles: new_message.mention_roles, - mention_everyone: new_message.mention_everyone, - pinned: new_message.pinned, - timestamp: new_message.timestamp, - edited_timestamp: new_message.edited_timestamp, + // TODO: a DTO? + return res.json({ + ...new_message.toJSON(), + id: new_message.id, + type: new_message.type, + channel_id: new_message.channel_id, + member: new_message.member?.toPublicMember(), + author: new_message.author?.toPublicUser(), + attachments: new_message.attachments, + embeds: new_message.embeds, + mentions: new_message.embeds, + mention_roles: new_message.mention_roles, + mention_everyone: new_message.mention_everyone, + pinned: new_message.pinned, + timestamp: new_message.timestamp, + edited_timestamp: new_message.edited_timestamp, - // these are not in the Discord.com response - mention_channels: new_message.mention_channels, - }); - }, + // these are not in the Discord.com response + mention_channels: new_message.mention_channels, + }); + }, ); // Backfill message with specific timestamp router.put( - "/", - messageUpload.single("file"), - async (req, res, next) => { - if (req.body.payload_json) { - req.body = JSON.parse(req.body.payload_json); - } + "/", + messageUpload.single("file"), + async (req, res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); + } - next(); - }, - route({ - requestBody: "MessageCreateSchema", - permission: "SEND_MESSAGES", - right: "SEND_BACKDATED_EVENTS", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; - const body = req.body as MessageCreateSchema; - const attachments: (MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? []; + next(); + }, + route({ + requestBody: "MessageCreateSchema", + permission: "SEND_MESSAGES", + right: "SEND_BACKDATED_EVENTS", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; + const body = req.body as MessageCreateSchema; + const attachments: (MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? []; - const rights = await getRights(req.user_id); - rights.hasThrow("SEND_MESSAGES"); + const rights = await getRights(req.user_id); + rights.hasThrow("SEND_MESSAGES"); - // regex to check if message contains anything other than numerals ( also no decimals ) - if (!message_id.match(/^\+?\d+$/)) { - throw new HTTPError("Message IDs must be positive integers", 400); - } + // regex to check if message contains anything other than numerals ( also no decimals ) + if (!message_id.match(/^\+?\d+$/)) { + throw new HTTPError("Message IDs must be positive integers", 400); + } - const snowflake = Snowflake.deconstruct(message_id); - if (Date.now() < snowflake.timestamp) { - // message is in the future - throw SpacebarApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE; - } + const snowflake = Snowflake.deconstruct(message_id); + if (Date.now() < snowflake.timestamp) { + // message is in the future + throw SpacebarApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE; + } - const exists = await Message.findOne({ - where: { id: message_id, channel_id: channel_id }, - }); - if (exists) { - throw SpacebarApiErrors.CANNOT_REPLACE_BY_BACKFILL; - } + const exists = await Message.findOne({ + where: { id: message_id, channel_id: channel_id }, + }); + if (exists) { + throw SpacebarApiErrors.CANNOT_REPLACE_BY_BACKFILL; + } - if (req.file) { - try { - const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); - attachments.push(Attachment.create({ ...file, proxy_url: file.url })); - } catch (error) { - return res.status(400).json(error); - } - } - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients", "recipients.user"], - }); + if (req.file) { + try { + const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); + attachments.push(Attachment.create({ ...file, proxy_url: file.url })); + } catch (error) { + return res.status(400).json(error); + } + } + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients", "recipients.user"], + }); - const embeds = body.embeds || []; - if (body.embed) embeds.push(body.embed); - const message = await handleMessage({ - ...body, - type: 0, - pinned: false, - author_id: req.user_id, - id: message_id, - embeds, - channel_id, - attachments, - edited_timestamp: undefined, - timestamp: new Date(snowflake.timestamp), - }); + const embeds = body.embeds || []; + if (body.embed) embeds.push(body.embed); + const message = await handleMessage({ + ...body, + type: 0, + pinned: false, + author_id: req.user_id, + id: message_id, + embeds, + channel_id, + attachments, + edited_timestamp: undefined, + timestamp: new Date(snowflake.timestamp), + }); - //Fix for the client bug - delete message.member; + //Fix for the client bug + delete message.member; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: channel_id, - data: message, - } as MessageCreateEvent), - channel.save(), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: channel_id, + data: message, + } as MessageCreateEvent), + channel.save(), + ]); - // no await as it shouldnt block the message send function and silently catch error - postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); + // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - return res.json( - message.withSignedAttachments( - new NewUrlUserSignatureData({ - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ), - ); - }, + return res.json( + message.withSignedAttachments( + new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ), + ); + }, ); router.get( - "/", - route({ - permission: "VIEW_CHANNEL", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; + "/", + route({ + permission: "VIEW_CHANNEL", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - relations: ["attachments"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + relations: ["attachments"], + }); - const permissions = await getPermission(req.user_id, undefined, channel_id); + const permissions = await getPermission(req.user_id, undefined, channel_id); - if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY"); + if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY"); - return res.json(message); - }, + return res.json(message); + }, ); router.delete( - "/", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; + "/", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const message = await Message.findOneOrFail({ - where: { id: message_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + }); - const rights = await getRights(req.user_id); + const rights = await getRights(req.user_id); - if (message.author_id !== req.user_id) { - if (!rights.has("MANAGE_MESSAGES")) { - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - permission.hasThrow("MANAGE_MESSAGES"); - } - } else rights.hasThrow("SELF_DELETE_MESSAGES"); + if (message.author_id !== req.user_id) { + if (!rights.has("MANAGE_MESSAGES")) { + const permission = await getPermission(req.user_id, channel.guild_id, channel_id); + permission.hasThrow("MANAGE_MESSAGES"); + } + } else rights.hasThrow("SELF_DELETE_MESSAGES"); - await Message.delete({ id: message_id }); + await Message.delete({ id: message_id }); - await emitEvent({ - event: "MESSAGE_DELETE", - channel_id, - data: { - id: message_id, - channel_id, - guild_id: channel.guild_id, - }, - } as MessageDeleteEvent); + await emitEvent({ + event: "MESSAGE_DELETE", + channel_id, + data: { + id: message_id, + channel_id, + guild_id: channel.guild_id, + }, + } as MessageDeleteEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/messages/\043message_id/reactions.ts" "b/src/api/routes/channels/\043channel_id/messages/\043message_id/reactions.ts" index 63c41cd..b21dbd3 100644 --- "a/src/api/routes/channels/\043channel_id/messages/\043message_id/reactions.ts" +++ "b/src/api/routes/channels/\043channel_id/messages/\043message_id/reactions.ts" @@ -18,18 +18,18 @@ import { route } from "@spacebar/api"; import { - Channel, - emitEvent, - Emoji, - getPermission, - Member, - Message, - MessageReactionAddEvent, - MessageReactionRemoveAllEvent, - MessageReactionRemoveEmojiEvent, - MessageReactionRemoveEvent, - User, - arrayRemove, + Channel, + emitEvent, + Emoji, + getPermission, + Member, + Message, + MessageReactionAddEvent, + MessageReactionRemoveAllEvent, + MessageReactionRemoveEmojiEvent, + MessageReactionRemoveEvent, + User, + arrayRemove, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -40,326 +40,326 @@ // TODO: check if emoji is really an unicode emoji or a properly encoded external emoji function getEmoji(emoji: string): PartialEmoji { - emoji = decodeURIComponent(emoji); - const parts = emoji.includes(":") && emoji.split(":"); - if (parts) - return { - name: parts[0], - id: parts[1], - }; + emoji = decodeURIComponent(emoji); + const parts = emoji.includes(":") && emoji.split(":"); + if (parts) + return { + name: parts[0], + id: parts[1], + }; - return { - id: undefined, - name: emoji, - }; + return { + id: undefined, + name: emoji, + }; } router.delete( - "/", - route({ - permission: "MANAGE_MESSAGES", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; + "/", + route({ + permission: "MANAGE_MESSAGES", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - await Message.update({ id: message_id, channel_id }, { reactions: [] }); + await Message.update({ id: message_id, channel_id }, { reactions: [] }); - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE_ALL", - channel_id, - data: { - channel_id, - message_id, - guild_id: channel.guild_id, - }, - } as MessageReactionRemoveAllEvent); + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE_ALL", + channel_id, + data: { + channel_id, + message_id, + guild_id: channel.guild_id, + }, + } as MessageReactionRemoveAllEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:emoji", - route({ - permission: "MANAGE_MESSAGES", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); + "/:emoji", + route({ + permission: "MANAGE_MESSAGES", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + const emoji = getEmoji(req.params.emoji); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added) throw new HTTPError("Reaction not found", 404); - arrayRemove(message.reactions, already_added); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + if (!already_added) throw new HTTPError("Reaction not found", 404); + arrayRemove(message.reactions, already_added); - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_REACTION_REMOVE_EMOJI", - channel_id, - data: { - channel_id, - message_id, - guild_id: message.guild_id, - emoji, - }, - } as MessageReactionRemoveEmojiEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_REACTION_REMOVE_EMOJI", + channel_id, + data: { + channel_id, + message_id, + guild_id: message.guild_id, + emoji, + }, + } as MessageReactionRemoveEmojiEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.get( - "/:emoji", - route({ - permission: "VIEW_CHANNEL", - responses: { - 200: { - body: "PublicUser", - }, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); + "/:emoji", + route({ + permission: "VIEW_CHANNEL", + responses: { + 200: { + body: "PublicUser", + }, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + const emoji = getEmoji(req.params.emoji); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); - const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!reaction) throw new HTTPError("Reaction not found", 404); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); + const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + if (!reaction) throw new HTTPError("Reaction not found", 404); - const users = ( - await User.find({ - where: { - id: In(reaction.user_ids), - }, - select: PublicUserProjection, - }) - ).map((user) => user.toPublicUser()); + const users = ( + await User.find({ + where: { + id: In(reaction.user_ids), + }, + select: PublicUserProjection, + }) + ).map((user) => user.toPublicUser()); - res.json(users); - }, + res.json(users); + }, ); router.put( - "/:emoji/:user_id", - route({ - permission: "READ_MESSAGE_HISTORY", - right: "SELF_ADD_REACTIONS", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id, user_id } = req.params; - if (user_id !== "@me") throw new HTTPError("Invalid user"); - const emoji = getEmoji(req.params.emoji); + "/:emoji/:user_id", + route({ + permission: "READ_MESSAGE_HISTORY", + right: "SELF_ADD_REACTIONS", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id, user_id } = req.params; + if (user_id !== "@me") throw new HTTPError("Invalid user"); + const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added) req.permission?.hasThrow("ADD_REACTIONS"); + if (!already_added) req.permission?.hasThrow("ADD_REACTIONS"); - if (emoji.id) { - const external_emoji = await Emoji.findOneOrFail({ - where: { id: emoji.id }, - }); - if (!already_added && channel.guild_id != external_emoji.guild_id) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS"); - emoji.animated = external_emoji.animated; - emoji.name = external_emoji.name; - } + if (emoji.id) { + const external_emoji = await Emoji.findOneOrFail({ + where: { id: emoji.id }, + }); + if (!already_added && channel.guild_id != external_emoji.guild_id) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS"); + emoji.animated = external_emoji.animated; + emoji.name = external_emoji.name; + } - if (already_added) { - if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error - already_added.count++; - already_added.user_ids.push(req.user_id); - } else - message.reactions.push({ - count: 1, - emoji, - user_ids: [req.user_id], - }); + if (already_added) { + if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error + already_added.count++; + already_added.user_ids.push(req.user_id); + } else + message.reactions.push({ + count: 1, + emoji, + user_ids: [req.user_id], + }); - await message.save(); + await message.save(); - const member = - channel.guild_id && - ( - await Member.findOneOrFail({ - where: { id: req.user_id }, - select: PublicMemberProjection, - }) - ).toPublicMember(); + const member = + channel.guild_id && + ( + await Member.findOneOrFail({ + where: { id: req.user_id }, + select: PublicMemberProjection, + }) + ).toPublicMember(); - await emitEvent({ - event: "MESSAGE_REACTION_ADD", - channel_id, - data: { - user_id: req.user_id, - channel_id, - message_id, - guild_id: channel.guild_id, - emoji, - member, - }, - } as MessageReactionAddEvent); + await emitEvent({ + event: "MESSAGE_REACTION_ADD", + channel_id, + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + member, + }, + } as MessageReactionAddEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:emoji/:user_id", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - let { user_id } = req.params; - const { message_id, channel_id } = req.params; + "/:emoji/:user_id", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + let { user_id } = req.params; + const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); + const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); - if (user_id === "@me") user_id = req.user_id; - else { - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - } + if (user_id === "@me") user_id = req.user_id; + else { + const permissions = await getPermission(req.user_id, undefined, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + } - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); - already_added.count--; + already_added.count--; - if (already_added.count <= 0) arrayRemove(message.reactions, already_added); - else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); + if (already_added.count <= 0) arrayRemove(message.reactions, already_added); + else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); - await message.save(); + await message.save(); - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE", - channel_id, - data: { - user_id: req.user_id, - channel_id, - message_id, - guild_id: channel.guild_id, - emoji, - }, - } as MessageReactionRemoveEvent); + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE", + channel_id, + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + }, + } as MessageReactionRemoveEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:emoji/:burst/:user_id", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - let { user_id } = req.params; - const { message_id, channel_id } = req.params; + "/:emoji/:burst/:user_id", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + let { user_id } = req.params; + const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); + const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); - if (user_id === "@me") user_id = req.user_id; - else { - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - } + if (user_id === "@me") user_id = req.user_id; + else { + const permissions = await getPermission(req.user_id, undefined, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + } - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); - already_added.count--; + already_added.count--; - if (already_added.count <= 0) arrayRemove(message.reactions, already_added); - else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); + if (already_added.count <= 0) arrayRemove(message.reactions, already_added); + else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); - await message.save(); + await message.save(); - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE", - channel_id, - data: { - user_id: req.user_id, - channel_id, - message_id, - guild_id: channel.guild_id, - emoji, - }, - } as MessageReactionRemoveEvent); + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE", + channel_id, + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + }, + } as MessageReactionRemoveEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/messages/bulk-delete.ts" "b/src/api/routes/channels/\043channel_id/messages/bulk-delete.ts" index 54801a4..f4645ab 100644 --- "a/src/api/routes/channels/\043channel_id/messages/bulk-delete.ts" +++ "b/src/api/routes/channels/\043channel_id/messages/bulk-delete.ts" @@ -29,48 +29,48 @@ // should this request fail, if you provide messages older than 14 days/invalid ids? ANSWER: NO // https://discord.com/developers/docs/resources/channel#bulk-delete-messages router.post( - "/", - route({ - requestBody: "BulkDeleteSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); + "/", + route({ + requestBody: "BulkDeleteSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); - const rights = await getRights(req.user_id); - rights.hasThrow("SELF_DELETE_MESSAGES"); + const rights = await getRights(req.user_id); + rights.hasThrow("SELF_DELETE_MESSAGES"); - const superuser = rights.has("MANAGE_MESSAGES"); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); + const superuser = rights.has("MANAGE_MESSAGES"); + const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); - const { maxBulkDelete } = Config.get().limits.message; + const { maxBulkDelete } = Config.get().limits.message; - const { messages } = req.body as { messages: string[] }; - if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete"); - if (!superuser) { - permission.hasThrow("MANAGE_MESSAGES"); - if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); - } + const { messages } = req.body as { messages: string[] }; + if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete"); + if (!superuser) { + permission.hasThrow("MANAGE_MESSAGES"); + if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); + } - await Message.delete(messages); + await Message.delete(messages); - await emitEvent({ - event: "MESSAGE_DELETE_BULK", - channel_id, - data: { ids: messages, channel_id, guild_id: channel.guild_id }, - } as MessageDeleteBulkEvent); + await emitEvent({ + event: "MESSAGE_DELETE_BULK", + channel_id, + data: { ids: messages, channel_id, guild_id: channel.guild_id }, + } as MessageDeleteBulkEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); diff --git "a/src/api/routes/channels/\043channel_id/messages/index.ts" "b/src/api/routes/channels/\043channel_id/messages/index.ts" index 2ce3774..272fa8a 100644 --- "a/src/api/routes/channels/\043channel_id/messages/index.ts" +++ "b/src/api/routes/channels/\043channel_id/messages/index.ts" @@ -18,29 +18,29 @@ import { handleMessage, postHandleMessage, route } from "@spacebar/api"; import { - Attachment, - AutomodRule, - AutomodTriggerTypes, - Channel, - Config, - DiscordApiErrors, - DmChannelDTO, - emitEvent, - FieldErrors, - getPermission, - getUrlSignature, - Member, - Message, - MessageCreateEvent, - NewUrlSignatureData, - NewUrlUserSignatureData, - ReadState, - Relationship, - Rights, - Snowflake, - stringGlobToRegexp, - uploadFile, - User, + Attachment, + AutomodRule, + AutomodTriggerTypes, + Channel, + Config, + DiscordApiErrors, + DmChannelDTO, + emitEvent, + FieldErrors, + getPermission, + getUrlSignature, + Member, + Message, + MessageCreateEvent, + NewUrlSignatureData, + NewUrlUserSignatureData, + ReadState, + Relationship, + Rights, + Snowflake, + stringGlobToRegexp, + uploadFile, + User, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -48,17 +48,17 @@ import { FindManyOptions, FindOperator, LessThan, MoreThan, MoreThanOrEqual } from "typeorm"; import { URL } from "url"; import { - AcknowledgeDeleteSchema, - AutomodCustomWordsRule, - AutomodRuleActionType, - AutomodRuleEventType, - isTextChannel, - MessageCreateAttachment, - MessageCreateCloudAttachment, - MessageCreateSchema, - Reaction, - ReadStateType, - RelationshipType, + AcknowledgeDeleteSchema, + AutomodCustomWordsRule, + AutomodRuleActionType, + AutomodRuleEventType, + isTextChannel, + MessageCreateAttachment, + MessageCreateCloudAttachment, + MessageCreateSchema, + Reaction, + ReadStateType, + RelationshipType, } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); @@ -66,221 +66,221 @@ // https://discord.com/developers/docs/resources/channel#create-message // get messages router.get( - "/", - route({ - query: { - around: { - type: "string", - }, - before: { - type: "string", - }, - after: { - type: "string", - }, - limit: { - type: "number", - description: "max number of messages to return (1-100). defaults to 50", - }, - }, - responses: { - 200: { - body: "APIMessageArray", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel) throw new HTTPError("Channel not found", 404); + "/", + route({ + query: { + around: { + type: "string", + }, + before: { + type: "string", + }, + after: { + type: "string", + }, + limit: { + type: "number", + description: "max number of messages to return (1-100). defaults to 50", + }, + }, + responses: { + 200: { + body: "APIMessageArray", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const channel_id = req.params.channel_id; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel) throw new HTTPError("Channel not found", 404); - isTextChannel(channel.type); - const around = req.query.around ? `${req.query.around}` : undefined; - const before = req.query.before ? `${req.query.before}` : undefined; - const after = req.query.after ? `${req.query.after}` : undefined; - const limit = Number(req.query.limit) || 50; - if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422); + isTextChannel(channel.type); + const around = req.query.around ? `${req.query.around}` : undefined; + const before = req.query.before ? `${req.query.before}` : undefined; + const after = req.query.after ? `${req.query.after}` : undefined; + const limit = Number(req.query.limit) || 50; + if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422); - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); - permissions.hasThrow("VIEW_CHANNEL"); - if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); + const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); + permissions.hasThrow("VIEW_CHANNEL"); + if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - const query: FindManyOptions & { - where: { id?: FindOperator | FindOperator[] }; - } = { - order: { timestamp: "DESC" }, - take: limit, - where: { channel_id }, - relations: [ - "author", - "webhook", - "application", - "mentions", - "mention_roles", - "mention_channels", - "sticker_items", - "attachments", - "referenced_message", - "referenced_message.author", - "referenced_message.webhook", - "referenced_message.application", - "referenced_message.mentions", - "referenced_message.mention_roles", - "referenced_message.mention_channels", - "referenced_message.sticker_items", - "referenced_message.attachments", - ], - }; + const query: FindManyOptions & { + where: { id?: FindOperator | FindOperator[] }; + } = { + order: { timestamp: "DESC" }, + take: limit, + where: { channel_id }, + relations: [ + "author", + "webhook", + "application", + "mentions", + "mention_roles", + "mention_channels", + "sticker_items", + "attachments", + "referenced_message", + "referenced_message.author", + "referenced_message.webhook", + "referenced_message.application", + "referenced_message.mentions", + "referenced_message.mention_roles", + "referenced_message.mention_channels", + "referenced_message.sticker_items", + "referenced_message.attachments", + ], + }; - let messages: Message[]; + let messages: Message[]; - if (around) { - query.take = Math.floor(limit / 2); - if (query.take != 0) { - const [right, left] = await Promise.all([ - Message.find({ - ...query, - where: { channel_id, id: LessThan(around) }, - }), - Message.find({ - ...query, - where: { channel_id, id: MoreThanOrEqual(around) }, - order: { timestamp: "ASC" }, - }), - ]); - left.push(...right); - messages = left.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); - } else { - query.take = 1; - const message = await Message.findOne({ - ...query, - where: { channel_id, id: around }, - }); - messages = message ? [message] : []; - } - } else { - if (after) { - if (BigInt(after) > BigInt(Snowflake.generate())) throw new HTTPError("after parameter must not be greater than current time", 422); + if (around) { + query.take = Math.floor(limit / 2); + if (query.take != 0) { + const [right, left] = await Promise.all([ + Message.find({ + ...query, + where: { channel_id, id: LessThan(around) }, + }), + Message.find({ + ...query, + where: { channel_id, id: MoreThanOrEqual(around) }, + order: { timestamp: "ASC" }, + }), + ]); + left.push(...right); + messages = left.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + } else { + query.take = 1; + const message = await Message.findOne({ + ...query, + where: { channel_id, id: around }, + }); + messages = message ? [message] : []; + } + } else { + if (after) { + if (BigInt(after) > BigInt(Snowflake.generate())) throw new HTTPError("after parameter must not be greater than current time", 422); - query.where.id = MoreThan(after); - query.order = { timestamp: "ASC" }; - } else if (before) { - if (BigInt(before) > BigInt(Snowflake.generate())) throw new HTTPError("before parameter must not be greater than current time", 422); + query.where.id = MoreThan(after); + query.order = { timestamp: "ASC" }; + } else if (before) { + if (BigInt(before) > BigInt(Snowflake.generate())) throw new HTTPError("before parameter must not be greater than current time", 422); - query.where.id = LessThan(before); - } + query.where.id = LessThan(before); + } - messages = await Message.find(query); - } + messages = await Message.find(query); + } - const endpoint = Config.get().cdn.endpointPublic; + const endpoint = Config.get().cdn.endpointPublic; - const ret = messages.map((x: Message) => { - x = x.toJSON(); + const ret = messages.map((x: Message) => { + x = x.toJSON(); - (x.reactions || []).forEach((y: Partial) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - if ((y.user_ids || []).includes(req.user_id)) y.me = true; - delete y.user_ids; - }); - if (!x.author) - x.author = User.create({ - id: "4", - discriminator: "0000", - username: "Spacebar Ghost", - public_flags: 0, - }); - x.attachments?.forEach((y: Attachment) => { - // dynamically set attachment proxy_url in case the endpoint changed - const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`; + (x.reactions || []).forEach((y: Partial) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + if ((y.user_ids || []).includes(req.user_id)) y.me = true; + delete y.user_ids; + }); + if (!x.author) + x.author = User.create({ + id: "4", + discriminator: "0000", + username: "Spacebar Ghost", + public_flags: 0, + }); + x.attachments?.forEach((y: Attachment) => { + // dynamically set attachment proxy_url in case the endpoint changed + const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`; - const url = new URL(uri); - if (endpoint) { - const newBase = new URL(endpoint); - url.protocol = newBase.protocol; - url.hostname = newBase.hostname; - url.port = newBase.port; - } + const url = new URL(uri); + if (endpoint) { + const newBase = new URL(endpoint); + url.protocol = newBase.protocol; + url.hostname = newBase.hostname; + url.port = newBase.port; + } - y.proxy_url = url.toString(); + y.proxy_url = url.toString(); - y.proxy_url = getUrlSignature( - new NewUrlSignatureData({ - url: y.proxy_url, - userAgent: req.headers["user-agent"], - ip: req.ip, - }), - ) - .applyToUrl(y.proxy_url) - .toString(); + y.proxy_url = getUrlSignature( + new NewUrlSignatureData({ + url: y.proxy_url, + userAgent: req.headers["user-agent"], + ip: req.ip, + }), + ) + .applyToUrl(y.proxy_url) + .toString(); - y.url = getUrlSignature( - new NewUrlSignatureData({ - url: y.url, - userAgent: req.headers["user-agent"], - ip: req.ip, - }), - ) - .applyToUrl(y.url) - .toString(); - }); + y.url = getUrlSignature( + new NewUrlSignatureData({ + url: y.url, + userAgent: req.headers["user-agent"], + ip: req.ip, + }), + ) + .applyToUrl(y.url) + .toString(); + }); - /** + /** Some clients ( discord.js ) only check if a property exists within the response, which causes errors when, say, the `application` property is `null`. **/ - // for (var curr in x) { - // if (x[curr] === null) - // delete x[curr]; - // } + // for (var curr in x) { + // if (x[curr] === null) + // delete x[curr]; + // } - return x; - }); + return x; + }); - await Promise.all( - ret - .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user) - .map(async (x: MessageCreateSchema) => { - x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } }); - }), - ); + await Promise.all( + ret + .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user) + .map(async (x: MessageCreateSchema) => { + x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } }); + }), + ); - // polyfill message references for old messages - await Promise.all( - ret - .filter((msg) => msg.message_reference && !msg.referenced_message?.id) - .map(async (msg) => { - const whereOptions: { id: string; guild_id?: string; channel_id?: string } = { - id: msg.message_reference!.message_id, - }; - if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id; - if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id; + // polyfill message references for old messages + await Promise.all( + ret + .filter((msg) => msg.message_reference && !msg.referenced_message?.id) + .map(async (msg) => { + const whereOptions: { id: string; guild_id?: string; channel_id?: string } = { + id: msg.message_reference!.message_id, + }; + if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id; + if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id; - msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] }); - }), - ); + msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] }); + }), + ); - return res.json(ret); - }, + return res.json(ret); + }, ); // TODO: config max upload size const messageUpload = multer({ - limits: { - fileSize: Config.get().limits.message.maxAttachmentSize, - fields: 10, - // files: 1 - }, - storage: multer.memoryStorage(), + limits: { + fileSize: Config.get().limits.message.maxAttachmentSize, + fields: 10, + // files: 1 + }, + storage: multer.memoryStorage(), }); // max upload 50 mb /** TODO: dynamically change limit of MessageCreateSchema with config @@ -292,213 +292,213 @@ **/ // Send message router.post( - "/", - messageUpload.any(), - (req, res, next) => { - if (req.body.payload_json) { - req.body = JSON.parse(req.body.payload_json); - } + "/", + messageUpload.any(), + (req, res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); + } - next(); - }, - route({ - requestBody: "MessageCreateSchema", - permission: "SEND_MESSAGES", - right: "SEND_MESSAGES", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const body = req.body as MessageCreateSchema; - const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? []; + next(); + }, + route({ + requestBody: "MessageCreateSchema", + permission: "SEND_MESSAGES", + right: "SEND_MESSAGES", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const body = req.body as MessageCreateSchema; + const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? []; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients", "recipients.user"], - }); - if (!channel.isWritable()) { - throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400); - } + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients", "recipients.user"], + }); + if (!channel.isWritable()) { + throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400); + } - // handle blocked users in dms - if (channel.recipients?.length == 2) { - const otherUser = channel.recipients.find((r) => r.user_id != req.user_id)?.user; - if (otherUser) { - const relationship = await Relationship.findOne({ - where: [ - { from_id: req.user_id, to_id: otherUser.id }, - { from_id: otherUser.id, to_id: req.user_id }, - ], - }); + // handle blocked users in dms + if (channel.recipients?.length == 2) { + const otherUser = channel.recipients.find((r) => r.user_id != req.user_id)?.user; + if (otherUser) { + const relationship = await Relationship.findOne({ + where: [ + { from_id: req.user_id, to_id: otherUser.id }, + { from_id: otherUser.id, to_id: req.user_id }, + ], + }); - if (relationship?.type === RelationshipType.blocked) { - throw DiscordApiErrors.CANNOT_MESSAGE_USER; - } - } - } + if (relationship?.type === RelationshipType.blocked) { + throw DiscordApiErrors.CANNOT_MESSAGE_USER; + } + } + } - if (body.nonce) { - const existing = await Message.findOne({ - where: { - nonce: body.nonce, - channel_id: channel.id, - author_id: req.user_id, - }, - }); - if (existing) { - return res.json(existing); - } - } + if (body.nonce) { + const existing = await Message.findOne({ + where: { + nonce: body.nonce, + channel_id: channel.id, + author_id: req.user_id, + }, + }); + if (existing) { + return res.json(existing); + } + } - if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) { - const limits = Config.get().limits; - if (limits.absoluteRate.sendMessage.enabled) { - const count = await Message.count({ - where: { - channel_id, - timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)), - }, - }); + if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) { + const limits = Config.get().limits; + if (limits.absoluteRate.sendMessage.enabled) { + const count = await Message.count({ + where: { + channel_id, + timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)), + }, + }); - if (count >= limits.absoluteRate.sendMessage.limit) - throw FieldErrors({ - channel_id: { - code: "TOO_MANY_MESSAGES", - message: req.t("common:toomany.MESSAGE"), - }, - }); - } - } + if (count >= limits.absoluteRate.sendMessage.limit) + throw FieldErrors({ + channel_id: { + code: "TOO_MANY_MESSAGES", + message: req.t("common:toomany.MESSAGE"), + }, + }); + } + } - const files = (req.files as Express.Multer.File[]) ?? []; - for (const currFile of files) { - try { - const file = await uploadFile(`/attachments/${channel.id}`, currFile); - attachments.push(Attachment.create({ ...file, proxy_url: file.url })); - } catch (error) { - return res.status(400).json({ message: error?.toString() }); - } - } + const files = (req.files as Express.Multer.File[]) ?? []; + for (const currFile of files) { + try { + const file = await uploadFile(`/attachments/${channel.id}`, currFile); + attachments.push(Attachment.create({ ...file, proxy_url: file.url })); + } catch (error) { + return res.status(400).json({ message: error?.toString() }); + } + } - const embeds = body.embeds || []; - if (body.embed) embeds.push(body.embed); - const message = await handleMessage({ - ...body, - type: 0, - pinned: false, - author_id: req.user_id, - embeds, - channel_id, - attachments, - timestamp: new Date(), - }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore dont care2 - message.edited_timestamp = null; + const embeds = body.embeds || []; + if (body.embed) embeds.push(body.embed); + const message = await handleMessage({ + ...body, + type: 0, + pinned: false, + author_id: req.user_id, + embeds, + channel_id, + attachments, + timestamp: new Date(), + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore dont care2 + message.edited_timestamp = null; - channel.last_message_id = message.id; + channel.last_message_id = message.id; - if (channel.isDm()) { - const channel_dto = await DmChannelDTO.from(channel); + if (channel.isDm()) { + const channel_dto = await DmChannelDTO.from(channel); - // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed - await Promise.all( - channel.recipients?.map((recipient) => { - if (recipient.closed) { - recipient.closed = false; - return Promise.all([ - recipient.save(), - emitEvent({ - event: "CHANNEL_CREATE", - data: channel_dto.excludedRecipients([recipient.user_id]), - user_id: recipient.user_id, - }), - ]); - } - }) || [], - ); - } + // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed + await Promise.all( + channel.recipients?.map((recipient) => { + if (recipient.closed) { + recipient.closed = false; + return Promise.all([ + recipient.save(), + emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id, + }), + ]); + } + }) || [], + ); + } - if (message.guild_id) { - // handleMessage will fetch the Member, but only if they are not guild owner. - // have to fetch ourselves otherwise. - if (!message.member) { - message.member = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id: message.guild_id }, - relations: ["roles"], - }); - } + if (message.guild_id) { + // handleMessage will fetch the Member, but only if they are not guild owner. + // have to fetch ourselves otherwise. + if (!message.member) { + message.member = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id: message.guild_id }, + relations: ["roles"], + }); + } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id); - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id); + } - let read_state = await ReadState.findOne({ - where: { user_id: req.user_id, channel_id }, - }); - if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); - read_state.last_message_id = message.id; - //It's a little more complicated than this but this'll do - read_state.mention_count = 0; + let read_state = await ReadState.findOne({ + where: { user_id: req.user_id, channel_id }, + }); + if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); + read_state.last_message_id = message.id; + //It's a little more complicated than this but this'll do + read_state.mention_count = 0; - await Promise.all([ - read_state.save(), - message.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: channel_id, - data: message, - } as MessageCreateEvent), - message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null, - channel.save(), - ]); + await Promise.all([ + read_state.save(), + message.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: channel_id, + data: message, + } as MessageCreateEvent), + message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null, + channel.save(), + ]); - // no await as it shouldnt block the message send function and silently catch error - postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); + // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - return res.json( - message.withSignedAttachments( - new NewUrlUserSignatureData({ - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ), - ); - }, + return res.json( + message.withSignedAttachments( + new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ), + ); + }, ); router.delete( - "/ack", - route({ - requestBody: "AcknowledgeDeleteSchema", - responses: { - 204: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; // not really a channel id if read_state_type != CHANNEL - const body = req.body as AcknowledgeDeleteSchema; - if (body.version != 2) return res.status(204).send(); - // TODO: handle other read state types - if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send(); + "/ack", + route({ + requestBody: "AcknowledgeDeleteSchema", + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; // not really a channel id if read_state_type != CHANNEL + const body = req.body as AcknowledgeDeleteSchema; + if (body.version != 2) return res.status(204).send(); + // TODO: handle other read state types + if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send(); - const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } }); - if (readState) { - await readState.remove(); - } + const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } }); + if (readState) { + await readState.remove(); + } - res.status(204).send(); - }, + res.status(204).send(); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/messages/pins/index.ts" "b/src/api/routes/channels/\043channel_id/messages/pins/index.ts" index 1d770bd..2355c8b 100644 --- "a/src/api/routes/channels/\043channel_id/messages/pins/index.ts" +++ "b/src/api/routes/channels/\043channel_id/messages/pins/index.ts" @@ -24,169 +24,169 @@ const router: Router = Router({ mergeParams: true }); router.put( - "/:message_id", - route({ - permission: "VIEW_CHANNEL", - responses: { - 204: {}, - 403: {}, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id }, - relations: ["author"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); - // * in dm channels anyone can pin messages -> only check for guilds - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + // * in dm channels anyone can pin messages -> only check for guilds + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Message.count({ - where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) }, - }); + const pinned_count = await Message.count({ + where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) }, + }); - const { maxPins } = Config.get().limits.channel; - if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); + const { maxPins } = Config.get().limits.channel; + if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); - message.pinned_at = new Date(); + message.pinned_at = new Date(); - const author = await User.getPublicUser(req.user_id); + const author = await User.getPublicUser(req.user_id); - const systemPinMessage = Message.create({ - timestamp: new Date(), - type: 6, - guild_id: message.guild_id, - channel_id: message.channel_id, - author, - message_reference: { - message_id: message.id, - channel_id: message.channel_id, - guild_id: message.guild_id, - }, - reactions: [], - attachments: [], - embeds: [], - sticker_items: [], - edited_timestamp: undefined, - mentions: [], - mention_channels: [], - mention_roles: [], - mention_everyone: false, - }); + const systemPinMessage = Message.create({ + timestamp: new Date(), + type: 6, + guild_id: message.guild_id, + channel_id: message.channel_id, + author, + message_reference: { + message_id: message.id, + channel_id: message.channel_id, + guild_id: message.guild_id, + }, + reactions: [], + attachments: [], + embeds: [], + sticker_items: [], + edited_timestamp: undefined, + mentions: [], + mention_channels: [], + mention_roles: [], + mention_everyone: false, + }); - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message, - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined, - }, - } as ChannelPinsUpdateEvent), - systemPinMessage.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: message.channel_id, - data: systemPinMessage, - } as MessageCreateEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + systemPinMessage.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: message.channel_id, + data: systemPinMessage, + } as MessageCreateEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:message_id", - route({ - permission: "VIEW_CHANNEL", - responses: { - 204: {}, - 403: {}, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id }, - relations: ["author"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - message.pinned_at = null; + message.pinned_at = null; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message, - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined, - }, - } as ChannelPinsUpdateEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.get( - "/", - route({ - permission: ["READ_MESSAGE_HISTORY"], - responses: { - 200: { - body: "APIMessageArray", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; + "/", + route({ + permission: ["READ_MESSAGE_HISTORY"], + responses: { + 200: { + body: "APIMessageArray", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const pins = await Message.find({ - where: { channel_id: channel_id, pinned_at: Not(IsNull()) }, - relations: ["author"], - order: { pinned_at: "DESC" }, - }); + const pins = await Message.find({ + where: { channel_id: channel_id, pinned_at: Not(IsNull()) }, + relations: ["author"], + order: { pinned_at: "DESC" }, + }); - const items = pins.map((message: Message) => ({ - message, - pinned_at: message.pinned_at, - })); + const items = pins.map((message: Message) => ({ + message, + pinned_at: message.pinned_at, + })); - res.send({ - items, - has_more: false, - }); - }, + res.send({ + items, + has_more: false, + }); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/messages/search.ts" "b/src/api/routes/channels/\043channel_id/messages/search.ts" index 7331945..bec9a44 100644 --- "a/src/api/routes/channels/\043channel_id/messages/search.ts" +++ "b/src/api/routes/channels/\043channel_id/messages/search.ts" @@ -27,116 +27,116 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildMessagesSearchResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 422: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { guild_id: req.params.guild_id }, - select: ["id"], - }); - const { - content, - // include_nsfw, // TODO - offset, - sort_order, - // sort_by, // TODO: Handle 'relevance' - limit, - author_id, - } = req.query; + "/", + route({ + responses: { + 200: { + body: "GuildMessagesSearchResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 422: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { guild_id: req.params.guild_id }, + select: ["id"], + }); + const { + content, + // include_nsfw, // TODO + offset, + sort_order, + // sort_by, // TODO: Handle 'relevance' + limit, + author_id, + } = req.query; - const parsedLimit = Number(limit) || 50; - if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); + const parsedLimit = Number(limit) || 50; + if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); - if (sort_order) { - if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1) - throw FieldErrors({ - sort_order: { - message: "Value must be one of ('desc', 'asc').", - code: "BASE_TYPE_CHOICES", - }, - }); // todo this is wrong - } + if (sort_order) { + if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1) + throw FieldErrors({ + sort_order: { + message: "Value must be one of ('desc', 'asc').", + code: "BASE_TYPE_CHOICES", + }, + }); // todo this is wrong + } - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id as string | undefined); - permissions.hasThrow("VIEW_CHANNEL"); - if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); + const permissions = await getPermission(req.user_id, channel.guild_id, channel_id as string | undefined); + permissions.hasThrow("VIEW_CHANNEL"); + if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); - const query: FindManyOptions = { - order: { - timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC", - }, - take: parsedLimit || 0, - where: { - guild: { - id: channel.guild_id, - }, - channel: { - id: channel_id, - }, - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], - skip: offset ? Number(offset) : 0, - }; - //@ts-ignore - query.where.channel = { id: channel_id }; + const query: FindManyOptions = { + order: { + timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC", + }, + take: parsedLimit || 0, + where: { + guild: { + id: channel.guild_id, + }, + channel: { + id: channel_id, + }, + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], + skip: offset ? Number(offset) : 0, + }; + //@ts-ignore + query.where.channel = { id: channel_id }; - //@ts-ignore - if (author_id) query.where.author = { id: author_id }; - //@ts-ignore - if (content) query.where.content = Like(`%${content}%`); + //@ts-ignore + if (author_id) query.where.author = { id: author_id }; + //@ts-ignore + if (content) query.where.content = Like(`%${content}%`); - const messages: Message[] = await Message.find(query); - delete query.take; - const total_results = await Message.count(query); + const messages: Message[] = await Message.find(query); + delete query.take; + const total_results = await Message.count(query); - const messagesDto = messages.map((x) => [ - { - id: x.id, - type: x.type, - content: x.content, - channel_id: x.channel_id, - author: { - id: x.author?.id, - username: x.author?.username, - avatar: x.author?.avatar, - avatar_decoration: null, - discriminator: x.author?.discriminator, - public_flags: x.author?.public_flags, - }, - attachments: x.attachments, - embeds: x.embeds, - mentions: x.mentions, - mention_roles: x.mention_roles, - pinned: x.pinned, - mention_everyone: x.mention_everyone, - tts: x.tts, - timestamp: x.timestamp, - edited_timestamp: x.edited_timestamp, - flags: x.flags, - components: x.components, - poll: x.poll, - hit: true, - }, - ]); + const messagesDto = messages.map((x) => [ + { + id: x.id, + type: x.type, + content: x.content, + channel_id: x.channel_id, + author: { + id: x.author?.id, + username: x.author?.username, + avatar: x.author?.avatar, + avatar_decoration: null, + discriminator: x.author?.discriminator, + public_flags: x.author?.public_flags, + }, + attachments: x.attachments, + embeds: x.embeds, + mentions: x.mentions, + mention_roles: x.mention_roles, + pinned: x.pinned, + mention_everyone: x.mention_everyone, + tts: x.tts, + timestamp: x.timestamp, + edited_timestamp: x.edited_timestamp, + flags: x.flags, + components: x.components, + poll: x.poll, + hit: true, + }, + ]); - return res.json({ - messages: messagesDto, - total_results, - }); - }, + return res.json({ + messages: messagesDto, + total_results, + }); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/permissions.ts" "b/src/api/routes/channels/\043channel_id/permissions.ts" index b45ff14..c03ca03 100644 --- "a/src/api/routes/channels/\043channel_id/permissions.ts" +++ "b/src/api/routes/channels/\043channel_id/permissions.ts" @@ -27,80 +27,80 @@ // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) router.put( - "/:overwrite_id", - route({ - requestBody: "ChannelPermissionOverwriteSchema", - permission: "MANAGE_ROLES", - responses: { - 204: {}, - 404: {}, - 501: {}, - 400: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, overwrite_id } = req.params; - const body = req.body as ChannelPermissionOverwriteSchema; + "/:overwrite_id", + route({ + requestBody: "ChannelPermissionOverwriteSchema", + permission: "MANAGE_ROLES", + responses: { + 204: {}, + 404: {}, + 501: {}, + 400: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, overwrite_id } = req.params; + const body = req.body as ChannelPermissionOverwriteSchema; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) throw new HTTPError("Channel not found", 404); + channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild); - if (body.type === ChannelPermissionOverwriteType.role) { - if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404); - } else if (body.type === ChannelPermissionOverwriteType.member) { - if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404); - } else throw new HTTPError("type not supported", 501); + if (body.type === ChannelPermissionOverwriteType.role) { + if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404); + } else if (body.type === ChannelPermissionOverwriteType.member) { + if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404); + } else throw new HTTPError("type not supported", 501); - let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id); - if (!overwrite) { - overwrite = { - id: overwrite_id, - type: body.type, - allow: "0", - deny: "0", - }; - channel.permission_overwrites?.push(overwrite); - } - overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0"))); - overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0"))); + let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id); + if (!overwrite) { + overwrite = { + id: overwrite_id, + type: body.type, + allow: "0", + deny: "0", + }; + channel.permission_overwrites?.push(overwrite); + } + overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0"))); + overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0"))); - await Promise.all([ - channel.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel, - } as ChannelUpdateEvent), - ]); + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel, + } as ChannelUpdateEvent), + ]); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); // TODO: check permission hierarchy router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES", responses: { 204: {}, 404: {} } }), async (req: Request, res: Response) => { - const { channel_id, overwrite_id } = req.params; + const { channel_id, overwrite_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel.guild_id) throw new HTTPError("Channel not found", 404); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - channel.permission_overwrites = channel.permission_overwrites?.filter((x) => x.id !== overwrite_id); + channel.permission_overwrites = channel.permission_overwrites?.filter((x) => x.id !== overwrite_id); - await Promise.all([ - channel.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel, - } as ChannelUpdateEvent), - ]); + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel, + } as ChannelUpdateEvent), + ]); - return res.sendStatus(204); + return res.sendStatus(204); }); export default router; diff --git "a/src/api/routes/channels/\043channel_id/pins.ts" "b/src/api/routes/channels/\043channel_id/pins.ts" index 4b62115..8e56a54 100644 --- "a/src/api/routes/channels/\043channel_id/pins.ts" +++ "b/src/api/routes/channels/\043channel_id/pins.ts" @@ -25,161 +25,161 @@ // This is the old endpoint router.put( - "/:message_id", - route({ - permission: "VIEW_CHANNEL", - responses: { - 204: {}, - 403: {}, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id }, - relations: ["author"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); - // * in dm channels anyone can pin messages -> only check for guilds - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + // * in dm channels anyone can pin messages -> only check for guilds + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Message.count({ - where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) }, - }); + const pinned_count = await Message.count({ + where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) }, + }); - const { maxPins } = Config.get().limits.channel; - if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); + const { maxPins } = Config.get().limits.channel; + if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); - message.pinned_at = new Date(); + message.pinned_at = new Date(); - const author = await User.getPublicUser(req.user_id); + const author = await User.getPublicUser(req.user_id); - const systemPinMessage = Message.create({ - timestamp: new Date(), - type: 6, - guild_id: message.guild_id, - channel_id: message.channel_id, - author, - message_reference: { - message_id: message.id, - channel_id: message.channel_id, - guild_id: message.guild_id, - }, - reactions: [], - attachments: [], - embeds: [], - sticker_items: [], - edited_timestamp: undefined, - mentions: [], - mention_channels: [], - mention_roles: [], - mention_everyone: false, - }); + const systemPinMessage = Message.create({ + timestamp: new Date(), + type: 6, + guild_id: message.guild_id, + channel_id: message.channel_id, + author, + message_reference: { + message_id: message.id, + channel_id: message.channel_id, + guild_id: message.guild_id, + }, + reactions: [], + attachments: [], + embeds: [], + sticker_items: [], + edited_timestamp: undefined, + mentions: [], + mention_channels: [], + mention_roles: [], + mention_everyone: false, + }); - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message, - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined, - }, - } as ChannelPinsUpdateEvent), - systemPinMessage.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: message.channel_id, - data: systemPinMessage, - } as MessageCreateEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + systemPinMessage.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: message.channel_id, + data: systemPinMessage, + } as MessageCreateEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:message_id", - route({ - permission: "VIEW_CHANNEL", - responses: { - 204: {}, - 403: {}, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id }, - relations: ["author"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - message.pinned_at = null; + message.pinned_at = null; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message, - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined, - }, - } as ChannelPinsUpdateEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.get( - "/", - route({ - permission: ["READ_MESSAGE_HISTORY"], - responses: { - 200: { - body: "APIMessageArray", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; + "/", + route({ + permission: ["READ_MESSAGE_HISTORY"], + responses: { + 200: { + body: "APIMessageArray", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const pins = await Message.find({ - where: { channel_id: channel_id, pinned_at: Not(IsNull()) }, - relations: ["author"], - order: { pinned_at: "DESC" }, - }); + const pins = await Message.find({ + where: { channel_id: channel_id, pinned_at: Not(IsNull()) }, + relations: ["author"], + order: { pinned_at: "DESC" }, + }); - res.send(pins); - }, + res.send(pins); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/purge.ts" "b/src/api/routes/channels/\043channel_id/purge.ts" index 9686bc2..3a46beb 100644 --- "a/src/api/routes/channels/\043channel_id/purge.ts" +++ "b/src/api/routes/channels/\043channel_id/purge.ts" @@ -31,71 +31,71 @@ TODO: apply the delete bit by bit to prevent client and database stress **/ router.post( - "/", - route({ - /*body: "PurgeSchema",*/ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + "/", + route({ + /*body: "PurgeSchema",*/ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400); - isTextChannel(channel.type); + if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400); + isTextChannel(channel.type); - const rights = await getRights(req.user_id); - if (!rights.has("MANAGE_MESSAGES")) { - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - permissions.hasThrow("MANAGE_CHANNELS"); - } + const rights = await getRights(req.user_id); + if (!rights.has("MANAGE_MESSAGES")) { + const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + permissions.hasThrow("MANAGE_CHANNELS"); + } - const { before, after } = req.body as PurgeSchema; + const { before, after } = req.body as PurgeSchema; - // TODO: send the deletion event bite-by-bite to prevent client stress + // TODO: send the deletion event bite-by-bite to prevent client stress - const query: FindManyOptions & { - where: { id?: FindOperator }; - } = { - order: { id: "ASC" }, - // take: limit, - where: { - channel_id, - id: Between(after, before), // the right way around - author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id), - // if you lack the right of self-deletion, you can't delete your own messages, even in purges - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], - }; + const query: FindManyOptions & { + where: { id?: FindOperator }; + } = { + order: { id: "ASC" }, + // take: limit, + where: { + channel_id, + id: Between(after, before), // the right way around + author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id), + // if you lack the right of self-deletion, you can't delete your own messages, even in purges + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], + }; - const messages = await Message.find(query); + const messages = await Message.find(query); - if (messages.length == 0) { - res.sendStatus(304); - return; - } + if (messages.length == 0) { + res.sendStatus(304); + return; + } - await Message.delete(messages.map((x) => x.id)); + await Message.delete(messages.map((x) => x.id)); - await emitEvent({ - event: "MESSAGE_DELETE_BULK", - channel_id, - data: { - ids: messages.map((x) => x.id), - channel_id, - guild_id: channel.guild_id, - }, - } as MessageDeleteBulkEvent); + await emitEvent({ + event: "MESSAGE_DELETE_BULK", + channel_id, + data: { + ids: messages.map((x) => x.id), + channel_id, + guild_id: channel.guild_id, + }, + } as MessageDeleteBulkEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); diff --git "a/src/api/routes/channels/\043channel_id/recipients.ts" "b/src/api/routes/channels/\043channel_id/recipients.ts" index 6388cfe..d0af3b4 100644 --- "a/src/api/routes/channels/\043channel_id/recipients.ts" +++ "b/src/api/routes/channels/\043channel_id/recipients.ts" @@ -24,79 +24,79 @@ const router: Router = Router({ mergeParams: true }); router.put( - "/:user_id", - route({ - responses: { - 201: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, user_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients"], - }); + "/:user_id", + route({ + responses: { + 201: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, user_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); - if (channel.type !== ChannelType.GROUP_DM) { - const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])]; + if (channel.type !== ChannelType.GROUP_DM) { + const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])]; - const new_channel = await Channel.createDMChannel(recipients, req.user_id); - return res.status(201).json(new_channel); - } else { - if (channel.recipients?.map((r) => r.user_id).includes(user_id)) { - throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? - } + const new_channel = await Channel.createDMChannel(recipients, req.user_id); + return res.status(201).json(new_channel); + } else { + if (channel.recipients?.map((r) => r.user_id).includes(user_id)) { + throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? + } - channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id })); - await channel.save(); + channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id })); + await channel.save(); - await emitEvent({ - event: "CHANNEL_CREATE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id, - }); + await emitEvent({ + event: "CHANNEL_CREATE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id, + }); - await emitEvent({ - event: "CHANNEL_RECIPIENT_ADD", - data: { - channel_id: channel_id, - user: await User.findOneOrFail({ - where: { id: user_id }, - select: PublicUserProjection, - }), - }, - channel_id: channel_id, - } as ChannelRecipientAddEvent); - return res.sendStatus(204); - } - }, + await emitEvent({ + event: "CHANNEL_RECIPIENT_ADD", + data: { + channel_id: channel_id, + user: await User.findOneOrFail({ + where: { id: user_id }, + select: PublicUserProjection, + }), + }, + channel_id: channel_id, + } as ChannelRecipientAddEvent); + return res.sendStatus(204); + } + }, ); router.delete( - "/:user_id", - route({ - responses: { - 204: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, user_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients"], - }); - if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) throw DiscordApiErrors.MISSING_PERMISSIONS; + "/:user_id", + route({ + responses: { + 204: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, user_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); + if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) throw DiscordApiErrors.MISSING_PERMISSIONS; - if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) { - throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? - } + if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) { + throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? + } - await Channel.removeRecipientFromChannel(channel, user_id); + await Channel.removeRecipientFromChannel(channel, user_id); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/typing.ts" "b/src/api/routes/channels/\043channel_id/typing.ts" index 8c36add..a41ab7f 100644 --- "a/src/api/routes/channels/\043channel_id/typing.ts" +++ "b/src/api/routes/channels/\043channel_id/typing.ts" @@ -23,47 +23,47 @@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - permission: "SEND_MESSAGES", - responses: { - 204: {}, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const user_id = req.user_id; - const timestamp = Math.floor(Date.now() / 1000); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const member = await Member.findOne({ - where: { id: user_id, guild_id: channel.guild_id }, - relations: ["roles", "user"], - }); - await emitEvent({ - event: "TYPING_START", - channel_id: channel_id, - data: { - ...(member - ? { - member: { - ...member.toPublicMember(), - roles: member?.roles?.map((x) => x.id), - }, - } - : null), - channel_id, - timestamp, - user_id, - guild_id: channel.guild_id, - }, - } as TypingStartEvent); + "/", + route({ + permission: "SEND_MESSAGES", + responses: { + 204: {}, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const user_id = req.user_id; + const timestamp = Math.floor(Date.now() / 1000); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const member = await Member.findOne({ + where: { id: user_id, guild_id: channel.guild_id }, + relations: ["roles", "user"], + }); + await emitEvent({ + event: "TYPING_START", + channel_id: channel_id, + data: { + ...(member + ? { + member: { + ...member.toPublicMember(), + roles: member?.roles?.map((x) => x.id), + }, + } + : null), + channel_id, + timestamp, + user_id, + guild_id: channel.guild_id, + }, + } as TypingStartEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/channels/\043channel_id/webhooks.ts" "b/src/api/routes/channels/\043channel_id/webhooks.ts" index 9d226b1..1c3c980 100644 --- "a/src/api/routes/channels/\043channel_id/webhooks.ts" +++ "b/src/api/routes/channels/\043channel_id/webhooks.ts" @@ -26,88 +26,88 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - description: "Returns a list of channel webhook objects. Requires the MANAGE_WEBHOOKS permission.", - permission: "MANAGE_WEBHOOKS", - responses: { - 200: { - body: "APIWebhookArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const webhooks = await Webhook.find({ - where: { channel_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + "/", + route({ + description: "Returns a list of channel webhook objects. Requires the MANAGE_WEBHOOKS permission.", + permission: "MANAGE_WEBHOOKS", + responses: { + 200: { + body: "APIWebhookArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const webhooks = await Webhook.find({ + where: { channel_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - return res.json( - webhooks.map((webhook) => ({ - ...webhook, - url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, - })), - ); - }, + return res.json( + webhooks.map((webhook) => ({ + ...webhook, + url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, + })), + ); + }, ); // TODO: use Image Data Type for avatar instead of String router.post( - "/", - route({ - requestBody: "WebhookCreateSchema", - permission: "MANAGE_WEBHOOKS", - responses: { - 200: { - body: "WebhookCreateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + "/", + route({ + requestBody: "WebhookCreateSchema", + permission: "MANAGE_WEBHOOKS", + responses: { + 200: { + body: "WebhookCreateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const channel_id = req.params.channel_id; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - isTextChannel(channel.type); - if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); + isTextChannel(channel.type); + if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); - const webhook_count = await Webhook.count({ where: { channel_id } }); - const { maxWebhooks } = Config.get().limits.channel; - if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); + const webhook_count = await Webhook.count({ where: { channel_id } }); + const { maxWebhooks } = Config.get().limits.channel; + if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - let { avatar, name } = req.body as WebhookCreateSchema; - name = trimSpecial(name); + let { avatar, name } = req.body as WebhookCreateSchema; + name = trimSpecial(name); - // TODO: move this - if (name) { - ValidateName(name); - } + // TODO: move this + if (name) { + ValidateName(name); + } - if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar); + if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar); - const hook = await Webhook.create({ - type: WebhookType.Incoming, - name, - avatar, - guild_id: channel.guild_id, - channel_id: channel.id, - user_id: req.user_id, - token: crypto.randomBytes(24).toString("base64url"), - }).save(); + const hook = await Webhook.create({ + type: WebhookType.Incoming, + name, + avatar, + guild_id: channel.guild_id, + channel_id: channel.id, + user_id: req.user_id, + token: crypto.randomBytes(24).toString("base64url"), + }).save(); - const user = await User.getPublicUser(req.user_id); + const user = await User.getPublicUser(req.user_id); - return res.json({ - ...hook, - user: user, - }); - }, + return res.json({ + ...hook, + user: user, + }); + }, ); export default router; diff --git a/src/api/routes/channels/preload-messages.ts b/src/api/routes/channels/preload-messages.ts index cdfc58f..6a19052 100644 --- a/src/api/routes/channels/preload-messages.ts +++ b/src/api/routes/channels/preload-messages.ts @@ -23,49 +23,49 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "PreloadMessagesRequestSchema", - responses: { - 200: { - body: "PreloadMessagesResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as PreloadMessagesRequestSchema; - if (body.channels.length > Config.get().limits.message.maxPreloadCount) - return res.status(400).send({ - code: 400, - message: `Cannot preload more than ${Config.get().limits.message.maxPreloadCount} channels at once.`, - }); + "/", + route({ + requestBody: "PreloadMessagesRequestSchema", + responses: { + 200: { + body: "PreloadMessagesResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as PreloadMessagesRequestSchema; + if (body.channels.length > Config.get().limits.message.maxPreloadCount) + return res.status(400).send({ + code: 400, + message: `Cannot preload more than ${Config.get().limits.message.maxPreloadCount} channels at once.`, + }); - const messages = ( - await Promise.all( - body.channels.map( - async (channelId) => - await Message.findOne({ - where: { channel_id: channelId }, - order: { timestamp: "DESC" }, - }), - ), - ) - ).filter((x) => x !== null) as Message[]; + const messages = ( + await Promise.all( + body.channels.map( + async (channelId) => + await Message.findOne({ + where: { channel_id: channelId }, + order: { timestamp: "DESC" }, + }), + ), + ) + ).filter((x) => x !== null) as Message[]; - const filteredMessages = messages.map((message) => { - const x = message.toJSON(); - // https://docs.discord.food/resources/message#preload-messages - reactions are not included in the response - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - x.reactions = undefined; - return x; - }) as PreloadMessagesResponseSchema; + const filteredMessages = messages.map((message) => { + const x = message.toJSON(); + // https://docs.discord.food/resources/message#preload-messages - reactions are not included in the response + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + x.reactions = undefined; + return x; + }) as PreloadMessagesResponseSchema; - return res.status(200).send(filteredMessages); - }, + return res.status(200).send(filteredMessages); + }, ); export default router; diff --git a/src/api/routes/collectibles-categories.ts b/src/api/routes/collectibles-categories.ts index d2cac39..ffeff09 100644 --- a/src/api/routes/collectibles-categories.ts +++ b/src/api/routes/collectibles-categories.ts @@ -23,18 +23,18 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "CollectiblesCategoriesResponse", - }, - 204: {}, - }, - }), - (req: Request, res: Response) => { - res.send([] as CollectiblesCategoriesResponse); - }, + "/", + route({ + responses: { + 200: { + body: "CollectiblesCategoriesResponse", + }, + 204: {}, + }, + }), + (req: Request, res: Response) => { + res.send([] as CollectiblesCategoriesResponse); + }, ); export default router; diff --git a/src/api/routes/collectibles-shop.ts b/src/api/routes/collectibles-shop.ts index b4c82f5..756c80c 100644 --- a/src/api/routes/collectibles-shop.ts +++ b/src/api/routes/collectibles-shop.ts @@ -23,21 +23,21 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "CollectiblesShopResponse", - }, - 204: {}, - }, - }), - (req: Request, res: Response) => { - res.send({ - shop_blocks: [], - categories: [], - } as CollectiblesShopResponse); - }, + "/", + route({ + responses: { + 200: { + body: "CollectiblesShopResponse", + }, + 204: {}, + }, + }), + (req: Request, res: Response) => { + res.send({ + shop_blocks: [], + categories: [], + } as CollectiblesShopResponse); + }, ); export default router; diff --git "a/src/api/routes/connections/\043connection_name/\043connection_id/refresh.ts" "b/src/api/routes/connections/\043connection_name/\043connection_id/refresh.ts" index 46f20ae..4a1a1cb 100644 --- "a/src/api/routes/connections/\043connection_name/\043connection_id/refresh.ts" +++ "b/src/api/routes/connections/\043connection_name/\043connection_id/refresh.ts" @@ -21,9 +21,9 @@ const router = Router({ mergeParams: true }); router.post("/", route({}), async (req: Request, res: Response) => { - // TODO: - // const { connection_name, connection_id } = req.params; - res.sendStatus(204); + // TODO: + // const { connection_name, connection_id } = req.params; + res.sendStatus(204); }); export default router; diff --git "a/src/api/routes/connections/\043connection_name/authorize.ts" "b/src/api/routes/connections/\043connection_name/authorize.ts" index b8e50db..1efb656 100644 --- "a/src/api/routes/connections/\043connection_name/authorize.ts" +++ "b/src/api/routes/connections/\043connection_name/authorize.ts" @@ -23,28 +23,28 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const { connection_name } = req.params; - const connection = ConnectionStore.connections.get(connection_name); - if (!connection) - throw FieldErrors({ - provider_id: { - code: "BASE_TYPE_CHOICES", - message: req.t("common:field.BASE_TYPE_CHOICES", { - types: Array.from(ConnectionStore.connections.keys()).join(", "), - }), - }, - }); + const { connection_name } = req.params; + const connection = ConnectionStore.connections.get(connection_name); + if (!connection) + throw FieldErrors({ + provider_id: { + code: "BASE_TYPE_CHOICES", + message: req.t("common:field.BASE_TYPE_CHOICES", { + types: Array.from(ConnectionStore.connections.keys()).join(", "), + }), + }, + }); - if (!connection.settings.enabled) - throw FieldErrors({ - provider_id: { - message: "This connection has been disabled server-side.", - }, - }); + if (!connection.settings.enabled) + throw FieldErrors({ + provider_id: { + message: "This connection has been disabled server-side.", + }, + }); - res.json({ - url: await connection.getAuthorizationUrl(req.user_id), - }); + res.json({ + url: await connection.getAuthorizationUrl(req.user_id), + }); }); export default router; diff --git "a/src/api/routes/connections/\043connection_name/callback.ts" "b/src/api/routes/connections/\043connection_name/callback.ts" index 5732a8e..a83882c 100644 --- "a/src/api/routes/connections/\043connection_name/callback.ts" +++ "b/src/api/routes/connections/\043connection_name/callback.ts" @@ -24,38 +24,38 @@ const router = Router({ mergeParams: true }); router.post("/", route({ requestBody: "ConnectionCallbackSchema" }), async (req: Request, res: Response) => { - const { connection_name } = req.params; - const connection = ConnectionStore.connections.get(connection_name); - if (!connection) - throw FieldErrors({ - provider_id: { - code: "BASE_TYPE_CHOICES", - message: req.t("common:field.BASE_TYPE_CHOICES", { - types: Array.from(ConnectionStore.connections.keys()).join(", "), - }), - }, - }); + const { connection_name } = req.params; + const connection = ConnectionStore.connections.get(connection_name); + if (!connection) + throw FieldErrors({ + provider_id: { + code: "BASE_TYPE_CHOICES", + message: req.t("common:field.BASE_TYPE_CHOICES", { + types: Array.from(ConnectionStore.connections.keys()).join(", "), + }), + }, + }); - if (!connection.settings.enabled) - throw FieldErrors({ - provider_id: { - message: "This connection has been disabled server-side.", - }, - }); + if (!connection.settings.enabled) + throw FieldErrors({ + provider_id: { + message: "This connection has been disabled server-side.", + }, + }); - const body = req.body as ConnectionCallbackSchema; - const userId = connection.getUserId(body.state); - const connectedAccnt = await connection.handleCallback(body); + const body = req.body as ConnectionCallbackSchema; + const userId = connection.getUserId(body.state); + const connectedAccnt = await connection.handleCallback(body); - // whether we should emit a connections update event, only used when a connection doesnt already exist - if (connectedAccnt) - emitEvent({ - event: "USER_CONNECTIONS_UPDATE", - data: { ...connectedAccnt, token_data: undefined }, - user_id: userId, - }); + // whether we should emit a connections update event, only used when a connection doesnt already exist + if (connectedAccnt) + emitEvent({ + event: "USER_CONNECTIONS_UPDATE", + data: { ...connectedAccnt, token_data: undefined }, + user_id: userId, + }); - res.sendStatus(204); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/connections/index.ts b/src/api/routes/connections/index.ts index b30d402..017ebbb 100644 --- a/src/api/routes/connections/index.ts +++ b/src/api/routes/connections/index.ts @@ -22,24 +22,24 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIConnectionsConfiguration", - }, - }, - }), - async (req: Request, res: Response) => { - const config = ConnectionConfig.get(); + "/", + route({ + responses: { + 200: { + body: "APIConnectionsConfiguration", + }, + }, + }), + async (req: Request, res: Response) => { + const config = ConnectionConfig.get(); - Object.keys(config).forEach((key) => { - delete config[key].clientId; - delete config[key].clientSecret; - }); + Object.keys(config).forEach((key) => { + delete config[key].clientId; + delete config[key].clientSecret; + }); - res.json(config); - }, + res.json(config); + }, ); export default router; diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts index ce34aa0..dcecec3 100644 --- a/src/api/routes/discoverable-guilds.ts +++ b/src/api/routes/discoverable-guilds.ts @@ -25,52 +25,52 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "DiscoverableGuildsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { offset, limit, categories } = req.query; - const showAllGuilds = Config.get().guild.discovery.showAllGuilds; - const configLimit = Config.get().guild.discovery.limit; - let guilds; - if (categories == undefined) { - guilds = showAllGuilds - ? await Guild.find({ - take: Math.abs(Number(limit || configLimit)), - }) - : await Guild.find({ - where: { features: Like(`%DISCOVERABLE%`) }, - take: Math.abs(Number(limit || configLimit)), - }); - } else { - guilds = showAllGuilds - ? await Guild.find({ - where: { primary_category_id: categories.toString() }, - take: Math.abs(Number(limit || configLimit)), - }) - : await Guild.find({ - where: { - primary_category_id: categories.toString(), - features: Like("%DISCOVERABLE%"), - }, - take: Math.abs(Number(limit || configLimit)), - }); - } + "/", + route({ + responses: { + 200: { + body: "DiscoverableGuildsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { offset, limit, categories } = req.query; + const showAllGuilds = Config.get().guild.discovery.showAllGuilds; + const configLimit = Config.get().guild.discovery.limit; + let guilds; + if (categories == undefined) { + guilds = showAllGuilds + ? await Guild.find({ + take: Math.abs(Number(limit || configLimit)), + }) + : await Guild.find({ + where: { features: Like(`%DISCOVERABLE%`) }, + take: Math.abs(Number(limit || configLimit)), + }); + } else { + guilds = showAllGuilds + ? await Guild.find({ + where: { primary_category_id: categories.toString() }, + take: Math.abs(Number(limit || configLimit)), + }) + : await Guild.find({ + where: { + primary_category_id: categories.toString(), + features: Like("%DISCOVERABLE%"), + }, + take: Math.abs(Number(limit || configLimit)), + }); + } - const total = guilds ? guilds.length : undefined; + const total = guilds ? guilds.length : undefined; - res.send({ - total: total, - guilds: guilds, - offset: Number(offset || Config.get().guild.discovery.offset), - limit: Number(limit || configLimit), - }); - }, + res.send({ + total: total, + guilds: guilds, + offset: Number(offset || Config.get().guild.discovery.offset), + limit: Number(limit || configLimit), + }); + }, ); export default router; diff --git a/src/api/routes/discovery.ts b/src/api/routes/discovery.ts index cb1e3a9..d865dfe 100644 --- a/src/api/routes/discovery.ts +++ b/src/api/routes/discovery.ts @@ -23,25 +23,25 @@ const router = Router({ mergeParams: true }); router.get( - "/categories", - route({ - responses: { - 200: { - body: "APIDiscoveryCategoryArray", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: - // Get locale instead + "/categories", + route({ + responses: { + 200: { + body: "APIDiscoveryCategoryArray", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: + // Get locale instead - // const { locale, primary_only } = req.query; - const { primary_only } = req.query; + // const { locale, primary_only } = req.query; + const { primary_only } = req.query; - const out = primary_only ? await Categories.find({ where: { is_primary: true } }) : await Categories.find(); + const out = primary_only ? await Categories.find({ where: { is_primary: true } }) : await Categories.find(); - res.send(out); - }, + res.send(out); + }, ); export default router; diff --git a/src/api/routes/download.ts b/src/api/routes/download.ts index 0658832..f54a3bb 100644 --- a/src/api/routes/download.ts +++ b/src/api/routes/download.ts @@ -23,36 +23,36 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 302: {}, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { platform } = req.query; + "/", + route({ + responses: { + 302: {}, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { platform } = req.query; - if (!platform) - throw FieldErrors({ - platform: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); + if (!platform) + throw FieldErrors({ + platform: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); - const release = await ClientRelease.findOneOrFail({ - where: { - enabled: true, - platform: platform as string, - }, - order: { pub_date: "DESC" }, - }); + const release = await ClientRelease.findOneOrFail({ + where: { + enabled: true, + platform: platform as string, + }, + order: { pub_date: "DESC" }, + }); - res.redirect(release.url); - }, + res.redirect(release.url); + }, ); export default router; diff --git "a/src/api/routes/emojis/\043emoji_id/source.ts" "b/src/api/routes/emojis/\043emoji_id/source.ts" index 7a5e1c7..f5c4a83 100644 --- "a/src/api/routes/emojis/\043emoji_id/source.ts" +++ "b/src/api/routes/emojis/\043emoji_id/source.ts" @@ -24,63 +24,63 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "EmojiSourceResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { emoji_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "EmojiSourceResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { emoji_id } = req.params; - const emoji = await Emoji.findOne({ where: { id: emoji_id } }); - if (!emoji) { - res.status(404).json({ - code: DiscordApiErrors.UNKNOWN_EMOJI.code, - message: `No emoji with ID ${emoji_id} appear to exist. Are you sure you didn't mistype it?`, - errors: {}, - } as APIErrorResponse); - return; - } + const emoji = await Emoji.findOne({ where: { id: emoji_id } }); + if (!emoji) { + res.status(404).json({ + code: DiscordApiErrors.UNKNOWN_EMOJI.code, + message: `No emoji with ID ${emoji_id} appear to exist. Are you sure you didn't mistype it?`, + errors: {}, + } as APIErrorResponse); + return; + } - // TODO: emojis can be owned by applications these days, account for this when we get there? - res.json({ - type: "GUILD", - guild: { - ...(await Guild.findOne({ - where: { - id: emoji.guild_id, - }, - select: { - id: true, - name: true, - icon: true, - description: true, - features: true, - emojis: true, - premium_tier: true, - premium_subscription_count: true, - }, - })), - approximate_member_count: await Member.countBy({ - guild_id: emoji.guild_id, - }), - approximate_presence_count: await Member.countBy({ - guild_id: emoji.guild_id, - user: { - sessions: { - status: "online", - }, - }, - }), - } as EmojiGuild, - } as EmojiSourceResponse); - }, + // TODO: emojis can be owned by applications these days, account for this when we get there? + res.json({ + type: "GUILD", + guild: { + ...(await Guild.findOne({ + where: { + id: emoji.guild_id, + }, + select: { + id: true, + name: true, + icon: true, + description: true, + features: true, + emojis: true, + premium_tier: true, + premium_subscription_count: true, + }, + })), + approximate_member_count: await Member.countBy({ + guild_id: emoji.guild_id, + }), + approximate_presence_count: await Member.countBy({ + guild_id: emoji.guild_id, + user: { + sessions: { + status: "online", + }, + }, + }), + } as EmojiGuild, + } as EmojiSourceResponse); + }, ); export default router; diff --git a/src/api/routes/experiments.ts b/src/api/routes/experiments.ts index bf12063..9dd54cc 100644 --- a/src/api/routes/experiments.ts +++ b/src/api/routes/experiments.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.send({ fingerprint: "", assignments: [], guild_experiments: [] }); + // TODO: + res.send({ fingerprint: "", assignments: [], guild_experiments: [] }); }); export default router; diff --git a/src/api/routes/games/detectable.ts b/src/api/routes/games/detectable.ts index 9e10bd5..e1415ea 100644 --- a/src/api/routes/games/detectable.ts +++ b/src/api/routes/games/detectable.ts @@ -22,33 +22,33 @@ const router: Router = Router({ mergeParams: true }); const cache = { - data: {}, - expires: 0, + data: {}, + expires: 0, }; // modern dclients call this, is /applications/detectable deprecated? router.get( - "/", - route({ - responses: { - 200: { - body: "ApplicationDetectableResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // cache for 6 hours - if (Date.now() > cache.expires) { - const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways - const data = await response.json(); - cache.data = data as ApplicationDetectableResponse; - cache.expires = Date.now() + 6 * 60 * 60 * 1000; - } + "/", + route({ + responses: { + 200: { + body: "ApplicationDetectableResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // cache for 6 hours + if (Date.now() > cache.expires) { + const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways + const data = await response.json(); + cache.data = data as ApplicationDetectableResponse; + cache.expires = Date.now() + 6 * 60 * 60 * 1000; + } - res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`) - .status(200) - .json(cache.data); - }, + res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`) + .status(200) + .json(cache.data); + }, ); export default router; diff --git a/src/api/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts index 80fa951..34a648c 100644 --- a/src/api/routes/gateway/bot.ts +++ b/src/api/routes/gateway/bot.ts @@ -23,27 +23,27 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GatewayBotResponse", - }, - }, - }), - (req: Request, res: Response) => { - const { endpointPublic } = Config.get().gateway; - res.json({ - url: endpointPublic, - shards: 1, - session_start_limit: { - total: 1000, - remaining: 999, - reset_after: 14400000, - max_concurrency: 1, - }, - }); - }, + "/", + route({ + responses: { + 200: { + body: "GatewayBotResponse", + }, + }, + }), + (req: Request, res: Response) => { + const { endpointPublic } = Config.get().gateway; + res.json({ + url: endpointPublic, + shards: 1, + session_start_limit: { + total: 1000, + remaining: 999, + reset_after: 14400000, + max_concurrency: 1, + }, + }); + }, ); export default router; diff --git a/src/api/routes/gateway/index.ts b/src/api/routes/gateway/index.ts index 1fafbf5..b85fcd9 100644 --- a/src/api/routes/gateway/index.ts +++ b/src/api/routes/gateway/index.ts @@ -23,20 +23,20 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GatewayResponse", - }, - }, - }), - (req: Request, res: Response) => { - const { endpointPublic } = Config.get().gateway; - res.json({ - url: endpointPublic, - }); - }, + "/", + route({ + responses: { + 200: { + body: "GatewayResponse", + }, + }, + }), + (req: Request, res: Response) => { + const { endpointPublic } = Config.get().gateway; + res.json({ + url: endpointPublic, + }); + }, ); export default router; diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts index e81c2c3..ca07956 100644 --- a/src/api/routes/gifs/search.ts +++ b/src/api/routes/gifs/search.ts @@ -25,45 +25,45 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - query: { - q: { - type: "string", - required: true, - description: "Search query", - }, - media_format: { - type: "string", - description: "Media format", - values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))), - }, - locale: { - type: "string", - description: "Locale", - }, - }, - responses: { - 200: { - body: "TenorGifsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: Custom providers - const { q, media_format, locale } = req.query; + "/", + route({ + query: { + q: { + type: "string", + required: true, + description: "Search query", + }, + media_format: { + type: "string", + description: "Media format", + values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))), + }, + locale: { + type: "string", + description: "Locale", + }, + }, + responses: { + 200: { + body: "TenorGifsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + const { q, media_format, locale } = req.query; - const apiKey = getGifApiKey(); + const apiKey = getGifApiKey(); - const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }); + const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }); - const { results } = (await response.json()) as { results: TenorGif[] }; + const { results } = (await response.json()) as { results: TenorGif[] }; - res.json(results.map(parseGifResult)).status(200); - }, + res.json(results.map(parseGifResult)).status(200); + }, ); export default router; diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts index d5907b4..99aa528 100644 --- a/src/api/routes/gifs/trending-gifs.ts +++ b/src/api/routes/gifs/trending-gifs.ts @@ -24,40 +24,40 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - query: { - media_format: { - type: "string", - description: "Media format", - values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))), - }, - locale: { - type: "string", - description: "Locale", - }, - }, - responses: { - 200: { - body: "TenorGifsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: Custom providers - const { media_format, locale } = req.query; + "/", + route({ + query: { + media_format: { + type: "string", + description: "Media format", + values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))), + }, + locale: { + type: "string", + description: "Locale", + }, + }, + responses: { + 200: { + body: "TenorGifsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + const { media_format, locale } = req.query; - const apiKey = getGifApiKey(); + const apiKey = getGifApiKey(); - const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }); + const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }); - const { results } = (await response.json()) as { results: TenorGif[] }; + const { results } = (await response.json()) as { results: TenorGif[] }; - res.json(results.map(parseGifResult)).status(200); - }, + res.json(results.map(parseGifResult)).status(200); + }, ); export default router; diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts index d86c06b..4a7a3f3 100644 --- a/src/api/routes/gifs/trending.ts +++ b/src/api/routes/gifs/trending.ts @@ -24,50 +24,50 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - query: { - locale: { - type: "string", - description: "Locale", - }, - }, - responses: { - 200: { - body: "TenorTrendingResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: Custom providers - // TODO: return gifs as mp4 - // const { media_format, locale } = req.query; - const { locale } = req.query; + "/", + route({ + query: { + locale: { + type: "string", + description: "Locale", + }, + }, + responses: { + 200: { + body: "TenorTrendingResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + // TODO: return gifs as mp4 + // const { media_format, locale } = req.query; + const { locale } = req.query; - const apiKey = getGifApiKey(); + const apiKey = getGifApiKey(); - const [responseSource, trendGifSource] = await Promise.all([ - fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }), - fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }), - ]); + const [responseSource, trendGifSource] = await Promise.all([ + fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }), + fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }), + ]); - const { tags } = (await responseSource.json()) as TenorCategoriesResults; - const { results } = (await trendGifSource.json()) as TenorTrendingResults; + const { tags } = (await responseSource.json()) as TenorCategoriesResults; + const { results } = (await trendGifSource.json()) as TenorTrendingResults; - res.json({ - categories: tags.map((x) => ({ - name: x.searchterm, - src: x.image, - })), - gifs: [parseGifResult(results[0])], - }).status(200); - }, + res.json({ + categories: tags.map((x) => ({ + name: x.searchterm, + src: x.image, + })), + gifs: [parseGifResult(results[0])], + }).status(200); + }, ); export default router; diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts index fa642ea..38ba102 100644 --- a/src/api/routes/guild-recommendations.ts +++ b/src/api/routes/guild-recommendations.ts @@ -25,32 +25,32 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildRecommendationsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // const { limit, personalization_disabled } = req.query; - const { limit } = req.query; - const showAllGuilds = Config.get().guild.discovery.showAllGuilds; + "/", + route({ + responses: { + 200: { + body: "GuildRecommendationsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // const { limit, personalization_disabled } = req.query; + const { limit } = req.query; + const showAllGuilds = Config.get().guild.discovery.showAllGuilds; - const genLoadId = (size: number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""); + const genLoadId = (size: number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""); - const guilds = showAllGuilds - ? await Guild.find({ take: Math.abs(Number(limit || 24)) }) - : await Guild.find({ - where: { features: Like("%DISCOVERABLE%") }, - take: Math.abs(Number(limit || 24)), - }); - res.send({ - recommended_guilds: guilds, - load_id: `server_recs/${genLoadId(32)}`, - }).status(200); - }, + const guilds = showAllGuilds + ? await Guild.find({ take: Math.abs(Number(limit || 24)) }) + : await Guild.find({ + where: { features: Like("%DISCOVERABLE%") }, + take: Math.abs(Number(limit || 24)), + }); + res.send({ + recommended_guilds: guilds, + load_id: `server_recs/${genLoadId(32)}`, + }).status(200); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/application-command-index.ts" "b/src/api/routes/guilds/\043guild_id/application-command-index.ts" index bb1d800..244029d 100644 --- "a/src/api/routes/guilds/\043guild_id/application-command-index.ts" +++ "b/src/api/routes/guilds/\043guild_id/application-command-index.ts" @@ -25,66 +25,66 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const members = await Member.find({ where: { guild_id: req.params.guild_id, user: { bot: true } } }); - const applications: Application[] = []; + const members = await Member.find({ where: { guild_id: req.params.guild_id, user: { bot: true } } }); + const applications: Application[] = []; - for (const member of members) { - const app = await Application.findOne({ where: { id: member.id } }); - if (app) applications.push(app); - } + for (const member of members) { + const app = await Application.findOne({ where: { id: member.id } }); + if (app) applications.push(app); + } - const applicationsSendable = []; + const applicationsSendable = []; - for (const application of applications) { - applicationsSendable.push({ - bot_id: application.bot?.id, - description: application.description, - flags: application.flags, - icon: application.icon, - id: application.id, - name: application.name, - }); - } + for (const application of applications) { + applicationsSendable.push({ + bot_id: application.bot?.id, + description: application.description, + flags: application.flags, + icon: application.icon, + id: application.id, + name: application.name, + }); + } - const applicationCommands: ApplicationCommand[][] = []; + const applicationCommands: ApplicationCommand[][] = []; - for (const application of applications) { - applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: IsNull() } })); - applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: req.params.guild_id } })); - } + for (const application of applications) { + applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: IsNull() } })); + applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: req.params.guild_id } })); + } - const applicationCommandsSendable: ApplicationCommandSchema[] = []; + const applicationCommandsSendable: ApplicationCommandSchema[] = []; - for (const command of applicationCommands.flat()) { - applicationCommandsSendable.push({ - id: command.id, - type: command.type, - application_id: command.application_id, - guild_id: command.guild_id, - name: command.name, - name_localizations: command.name_localizations, - // name_localized: // TODO: make this work - description: command.description, - description_localizations: command.description_localizations, - // description_localized: // TODO: make this work - options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined, - default_member_permissions: command.default_member_permissions, - dm_permission: command.dm_permission, - permissions: command.permissions, - nsfw: command.nsfw, - integration_types: command.integration_types, - global_popularity_rank: command.global_popularity_rank, - contexts: command.contexts, - version: command.version, - handler: command.handler, - }); - } + for (const command of applicationCommands.flat()) { + applicationCommandsSendable.push({ + id: command.id, + type: command.type, + application_id: command.application_id, + guild_id: command.guild_id, + name: command.name, + name_localizations: command.name_localizations, + // name_localized: // TODO: make this work + description: command.description, + description_localizations: command.description_localizations, + // description_localized: // TODO: make this work + options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined, + default_member_permissions: command.default_member_permissions, + dm_permission: command.dm_permission, + permissions: command.permissions, + nsfw: command.nsfw, + integration_types: command.integration_types, + global_popularity_rank: command.global_popularity_rank, + contexts: command.contexts, + version: command.version, + handler: command.handler, + }); + } - res.send({ - applications: applicationsSendable, - application_commands: applicationCommandsSendable, - version: Snowflake.generate(), - }); + res.send({ + applications: applicationsSendable, + application_commands: applicationCommandsSendable, + version: Snowflake.generate(), + }); }); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/audit-logs.ts" "b/src/api/routes/guilds/\043guild_id/audit-logs.ts" index 620f882..4acc8e5 100644 --- "a/src/api/routes/guilds/\043guild_id/audit-logs.ts" +++ "b/src/api/routes/guilds/\043guild_id/audit-logs.ts" @@ -22,14 +22,14 @@ //TODO: implement audit logs router.get("/", route({}), async (req: Request, res: Response) => { - res.json({ - audit_log_entries: [], - users: [], - integrations: [], - webhooks: [], - guild_scheduled_events: [], - threads: [], - application_commands: [], - }); + res.json({ + audit_log_entries: [], + users: [], + integrations: [], + webhooks: [], + guild_scheduled_events: [], + threads: [], + application_commands: [], + }); }); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/auto-moderation/rules.ts" "b/src/api/routes/guilds/\043guild_id/auto-moderation/rules.ts" index e2b3732..8c24651 100644 --- "a/src/api/routes/guilds/\043guild_id/auto-moderation/rules.ts" +++ "b/src/api/routes/guilds/\043guild_id/auto-moderation/rules.ts" @@ -25,116 +25,116 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - permission: ["MANAGE_GUILD"], - responses: { - 200: { - body: "AutomodRuleSchemaWithId[]", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const rules = await AutomodRule.find({ where: { guild_id } }); - return res.json(rules); - }, + "/", + route({ + permission: ["MANAGE_GUILD"], + responses: { + 200: { + body: "AutomodRuleSchemaWithId[]", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const rules = await AutomodRule.find({ where: { guild_id } }); + return res.json(rules); + }, ); router.post( - "/", - route({ - // requestBody: "AutomodRuleSchema", - permission: ["MANAGE_GUILD"], - responses: { - 200: { - body: "AutomodRuleSchemaWithId", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403); + "/", + route({ + // requestBody: "AutomodRuleSchema", + permission: ["MANAGE_GUILD"], + responses: { + 200: { + body: "AutomodRuleSchemaWithId", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403); - if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403); + if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403); - if (req.body.id) { - throw new HTTPError("You can't specify an ID for a new rule", 400); - } + if (req.body.id) { + throw new HTTPError("You can't specify an ID for a new rule", 400); + } - const data = req.body as AutomodRuleSchema; + const data = req.body as AutomodRuleSchema; - const created = AutomodRule.create({ - creator: await User.findOneOrFail({ - where: { id: data.creator_id }, - }), - ...data, - }); + const created = AutomodRule.create({ + creator: await User.findOneOrFail({ + where: { id: data.creator_id }, + }), + ...data, + }); - const savedRule = await AutomodRule.save(created); - return res.json(savedRule); - }, + const savedRule = await AutomodRule.save(created); + return res.json(savedRule); + }, ); router.patch( - "/:rule_id", - route({ - // requestBody: "AutomodRuleSchema - permission: ["MANAGE_GUILD"], - responses: { - 200: { - body: "AutomodRuleSchemaWithId", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { rule_id } = req.params; - const rule = await AutomodRule.findOneOrFail({ - where: { id: rule_id }, - }); + "/:rule_id", + route({ + // requestBody: "AutomodRuleSchema + permission: ["MANAGE_GUILD"], + responses: { + 200: { + body: "AutomodRuleSchemaWithId", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { rule_id } = req.params; + const rule = await AutomodRule.findOneOrFail({ + where: { id: rule_id }, + }); - const data = req.body as AutomodRuleSchema; + const data = req.body as AutomodRuleSchema; - AutomodRule.merge(rule, data); - const savedRule = await AutomodRule.save(rule); - return res.json(savedRule); - }, + AutomodRule.merge(rule, data); + const savedRule = await AutomodRule.save(rule); + return res.json(savedRule); + }, ); router.delete( - "/:rule_id", - route({ - permission: ["MANAGE_GUILD"], - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { rule_id } = req.params; - await AutomodRule.delete({ id: rule_id }); - return res.status(204).send(); - }, + "/:rule_id", + route({ + permission: ["MANAGE_GUILD"], + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { rule_id } = req.params; + await AutomodRule.delete({ id: rule_id }); + return res.status(204).send(); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/bans.ts" "b/src/api/routes/guilds/\043guild_id/bans.ts" index 03b4ede..cf043b7 100644 --- "a/src/api/routes/guilds/\043guild_id/bans.ts" +++ "b/src/api/routes/guilds/\043guild_id/bans.ts" @@ -27,267 +27,267 @@ /* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */ router.get( - "/", - route({ - permission: "BAN_MEMBERS", - responses: { - 200: { - body: "APIBansArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + permission: "BAN_MEMBERS", + responses: { + 200: { + body: "APIBansArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - let bans = await Ban.find({ where: { guild_id: guild_id } }); - const promisesToAwait: Promise[] = []; - const bansObj: APIBansArray = []; + let bans = await Ban.find({ where: { guild_id: guild_id } }); + const promisesToAwait: Promise[] = []; + const bansObj: APIBansArray = []; - bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing + bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing - bans.forEach((ban) => { - promisesToAwait.push(User.getPublicUser(ban.user_id)); - }); + bans.forEach((ban) => { + promisesToAwait.push(User.getPublicUser(ban.user_id)); + }); - const bannedUsers = await Promise.all(promisesToAwait); + const bannedUsers = await Promise.all(promisesToAwait); - bans.forEach((ban, index) => { - const user = bannedUsers[index]; - bansObj.push({ - reason: ban.reason ?? null, - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - avatar: user.avatar ?? null, - public_flags: user.public_flags, - }, - }); - }); + bans.forEach((ban, index) => { + const user = bannedUsers[index]; + bansObj.push({ + reason: ban.reason ?? null, + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + avatar: user.avatar ?? null, + public_flags: user.public_flags, + }, + }); + }); - return res.json(bansObj); - }, + return res.json(bansObj); + }, ); router.get( - "/search", - route({ - permission: "BAN_MEMBERS", - query: { - query: { - type: "string", - description: "Query to match username(s) and display name(s) against (1-32 characters)", - required: true, - }, - limit: { - type: "number", - description: "Max number of members to return (1-10, default 10)", - required: false, - }, - }, - responses: { - 200: { - body: "APIBansArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/search", + route({ + permission: "BAN_MEMBERS", + query: { + query: { + type: "string", + description: "Query to match username(s) and display name(s) against (1-32 characters)", + required: true, + }, + limit: { + type: "number", + description: "Max number of members to return (1-10, default 10)", + required: false, + }, + }, + responses: { + 200: { + body: "APIBansArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const limit = Number(req.query.limit) || 10; - if (limit > 10 || limit < 1) throw new HTTPError("Limit must be between 1 and 10"); + const limit = Number(req.query.limit) || 10; + if (limit > 10 || limit < 1) throw new HTTPError("Limit must be between 1 and 10"); - const query = String(req.query.query); - if (!query || query.trim().length === 0 || query.length > 32) { - throw new HTTPError("The query must be between 1 and 32 characters in length"); - } + const query = String(req.query.query); + if (!query || query.trim().length === 0 || query.length > 32) { + throw new HTTPError("The query must be between 1 and 32 characters in length"); + } - let bans = await Ban.createQueryBuilder("ban") - .leftJoinAndSelect("ban.user", "user") - .where("ban.guild_id = :guildId", { guildId: guild_id }) - .andWhere("user.username LIKE :userName", { - userName: `%${query}%`, - }) - .limit(limit) - .getMany(); + let bans = await Ban.createQueryBuilder("ban") + .leftJoinAndSelect("ban.user", "user") + .where("ban.guild_id = :guildId", { guildId: guild_id }) + .andWhere("user.username LIKE :userName", { + userName: `%${query}%`, + }) + .limit(limit) + .getMany(); - bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing + bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing - const bansObj: APIBansArray = bans.map((ban) => { - const user = ban.user; - return { - reason: ban.reason ?? null, - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - avatar: user.avatar ?? null, - public_flags: user.public_flags, - }, - }; - }); + const bansObj: APIBansArray = bans.map((ban) => { + const user = ban.user; + return { + reason: ban.reason ?? null, + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + avatar: user.avatar ?? null, + public_flags: user.public_flags, + }, + }; + }); - return res.json(bansObj); - }, + return res.json(bansObj); + }, ); router.get( - "/:user_id", - route({ - permission: "BAN_MEMBERS", - responses: { - 200: { - body: "GuildBansResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, user_id } = req.params; + "/:user_id", + route({ + permission: "BAN_MEMBERS", + responses: { + 200: { + body: "GuildBansResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, user_id } = req.params; - const ban = (await Ban.findOneOrFail({ - where: { guild_id: guild_id, user_id: user_id }, - })) as BanRegistrySchema; + const ban = (await Ban.findOneOrFail({ + where: { guild_id: guild_id, user_id: user_id }, + })) as BanRegistrySchema; - if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; - // pretend self-bans don't exist to prevent victim chasing + if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; + // pretend self-bans don't exist to prevent victim chasing - const user = await User.getPublicUser(ban.user_id); + const user = await User.getPublicUser(ban.user_id); - const banInfo: GuildBansResponse = { - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - avatar: user.avatar ?? null, - public_flags: user.public_flags, - }, - reason: ban.reason ?? null, - }; + const banInfo: GuildBansResponse = { + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + avatar: user.avatar ?? null, + public_flags: user.public_flags, + }, + reason: ban.reason ?? null, + }; - return res.json(banInfo); - }, + return res.json(banInfo); + }, ); router.put( - "/:user_id", - route({ - requestBody: "BanCreateSchema", - permission: "BAN_MEMBERS", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const banned_user_id = req.params.user_id; - const opts = req.body as BanCreateSchema; + "/:user_id", + route({ + requestBody: "BanCreateSchema", + permission: "BAN_MEMBERS", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const banned_user_id = req.params.user_id; + const opts = req.body as BanCreateSchema; - let deleteMessagesMs = opts.delete_message_days - ? (opts.delete_message_days as number) * 86400000 - : opts.delete_message_seconds - ? (opts.delete_message_seconds as number) * 1000 - : 0; + let deleteMessagesMs = opts.delete_message_days + ? (opts.delete_message_days as number) * 86400000 + : opts.delete_message_seconds + ? (opts.delete_message_seconds as number) * 1000 + : 0; - if (deleteMessagesMs < 0) deleteMessagesMs = 0; + if (deleteMessagesMs < 0) deleteMessagesMs = 0; - if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) - throw new HTTPError("You are the guild owner, hence can't ban yourself", 403); + if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) + throw new HTTPError("You are the guild owner, hence can't ban yourself", 403); - if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); + if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); - const existingBan = await Ban.findOne({ - where: { guild_id: guild_id, user_id: banned_user_id }, - }); + const existingBan = await Ban.findOne({ + where: { guild_id: guild_id, user_id: banned_user_id }, + }); - // Bans on already banned users are silently ignored - if (existingBan) return res.status(204).send(); + // Bans on already banned users are silently ignored + if (existingBan) return res.status(204).send(); - const banned_user = await User.getPublicUser(banned_user_id); + const banned_user = await User.getPublicUser(banned_user_id); - const ban = Ban.create({ - user_id: banned_user_id, - guild_id: guild_id, - executor_id: req.user_id, - reason: req.body.reason, // || otherwise empty - }); + const ban = Ban.create({ + user_id: banned_user_id, + guild_id: guild_id, + executor_id: req.user_id, + reason: req.body.reason, // || otherwise empty + }); - await Promise.all([ - Member.removeFromGuild(banned_user_id, guild_id), - ban.save(), - emitEvent({ - event: "GUILD_BAN_ADD", - data: { - guild_id: guild_id, - user: banned_user.toPublicUser(), - delete_message_secs: Math.floor(deleteMessagesMs / 1000), - }, - guild_id: guild_id, - } as GuildBanAddEvent), - ]); + await Promise.all([ + Member.removeFromGuild(banned_user_id, guild_id), + ban.save(), + emitEvent({ + event: "GUILD_BAN_ADD", + data: { + guild_id: guild_id, + user: banned_user.toPublicUser(), + delete_message_secs: Math.floor(deleteMessagesMs / 1000), + }, + guild_id: guild_id, + } as GuildBanAddEvent), + ]); - return res.status(204).send(); - }, + return res.status(204).send(); + }, ); router.delete( - "/:user_id", - route({ - permission: "BAN_MEMBERS", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, user_id } = req.params; + "/:user_id", + route({ + permission: "BAN_MEMBERS", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, user_id } = req.params; - await Ban.findOneOrFail({ - where: { guild_id: guild_id, user_id: user_id }, - }); + await Ban.findOneOrFail({ + where: { guild_id: guild_id, user_id: user_id }, + }); - const banned_user = await User.getPublicUser(user_id); + const banned_user = await User.getPublicUser(user_id); - await Promise.all([ - Ban.delete({ - user_id: user_id, - guild_id, - }), + await Promise.all([ + Ban.delete({ + user_id: user_id, + guild_id, + }), - emitEvent({ - event: "GUILD_BAN_REMOVE", - data: { - guild_id, - user: banned_user, - }, - guild_id, - } as GuildBanRemoveEvent), - ]); + emitEvent({ + event: "GUILD_BAN_REMOVE", + data: { + guild_id, + user: banned_user, + }, + guild_id, + } as GuildBanRemoveEvent), + ]); - return res.status(204).send(); - }, + return res.status(204).send(); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/bulk-ban.ts" "b/src/api/routes/guilds/\043guild_id/bulk-ban.ts" index a564e2d..004c2ab 100644 --- "a/src/api/routes/guilds/\043guild_id/bulk-ban.ts" +++ "b/src/api/routes/guilds/\043guild_id/bulk-ban.ts" @@ -25,93 +25,93 @@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "BulkBanSchema", - permission: ["BAN_MEMBERS", "MANAGE_GUILD"], - responses: { - 200: { - body: "Ban", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + requestBody: "BulkBanSchema", + permission: ["BAN_MEMBERS", "MANAGE_GUILD"], + responses: { + 200: { + body: "Ban", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const userIds: Array = req.body.user_ids; - if (!userIds) throw new HTTPError("The user_ids array is missing", 400); + const userIds: Array = req.body.user_ids; + if (!userIds) throw new HTTPError("The user_ids array is missing", 400); - if (userIds.length > Config.get().limits.guild.maxBulkBanUsers) throw new HTTPError("The user_ids array must be between 1 and 200 in length", 400); + if (userIds.length > Config.get().limits.guild.maxBulkBanUsers) throw new HTTPError("The user_ids array must be between 1 and 200 in length", 400); - const banned_users = []; - const failed_users = []; - for await (const banned_user_id of userIds) { - if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) { - failed_users.push(banned_user_id); - continue; - } + const banned_users = []; + const failed_users = []; + for await (const banned_user_id of userIds) { + if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) { + failed_users.push(banned_user_id); + continue; + } - if (req.permission?.cache.guild?.owner_id === banned_user_id) { - failed_users.push(banned_user_id); - continue; - } + if (req.permission?.cache.guild?.owner_id === banned_user_id) { + failed_users.push(banned_user_id); + continue; + } - const existingBan = await Ban.findOne({ - where: { guild_id: guild_id, user_id: banned_user_id }, - }); - if (existingBan) { - failed_users.push(banned_user_id); - continue; - } + const existingBan = await Ban.findOne({ + where: { guild_id: guild_id, user_id: banned_user_id }, + }); + if (existingBan) { + failed_users.push(banned_user_id); + continue; + } - let banned_user; - try { - banned_user = await User.getPublicUser(banned_user_id); - } catch { - failed_users.push(banned_user_id); - continue; - } + let banned_user; + try { + banned_user = await User.getPublicUser(banned_user_id); + } catch { + failed_users.push(banned_user_id); + continue; + } - const ban = Ban.create({ - user_id: banned_user_id, - guild_id: guild_id, - ip: req.ip, - executor_id: req.user_id, - reason: req.body.reason, // || otherwise empty - }); + const ban = Ban.create({ + user_id: banned_user_id, + guild_id: guild_id, + ip: req.ip, + executor_id: req.user_id, + reason: req.body.reason, // || otherwise empty + }); - try { - await Promise.all([ - Member.removeFromGuild(banned_user_id, guild_id), - ban.save(), - emitEvent({ - event: "GUILD_BAN_ADD", - data: { - guild_id: guild_id, - user: banned_user.toPublicUser(), - }, - guild_id: guild_id, - } as GuildBanAddEvent), - ]); - banned_users.push(banned_user_id); - } catch { - failed_users.push(banned_user_id); - continue; - } - } + try { + await Promise.all([ + Member.removeFromGuild(banned_user_id, guild_id), + ban.save(), + emitEvent({ + event: "GUILD_BAN_ADD", + data: { + guild_id: guild_id, + user: banned_user.toPublicUser(), + }, + guild_id: guild_id, + } as GuildBanAddEvent), + ]); + banned_users.push(banned_user_id); + } catch { + failed_users.push(banned_user_id); + continue; + } + } - if (banned_users.length === 0 && failed_users.length > 0) throw DiscordApiErrors.BULK_BAN_FAILED; - return res.json({ - banned_users: banned_users, - failed_users: failed_users, - }); - }, + if (banned_users.length === 0 && failed_users.length > 0) throw DiscordApiErrors.BULK_BAN_FAILED; + return res.json({ + banned_users: banned_users, + failed_users: failed_users, + }); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/channels.ts" "b/src/api/routes/guilds/\043guild_id/channels.ts" index ecb25be..fc374ac 100644 --- "a/src/api/routes/guilds/\043guild_id/channels.ts" +++ "b/src/api/routes/guilds/\043guild_id/channels.ts" @@ -23,148 +23,148 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 201: { - body: "APIChannelArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const channels = await Channel.find({ where: { guild_id } }); + "/", + route({ + responses: { + 201: { + body: "APIChannelArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const channels = await Channel.find({ where: { guild_id } }); - for await (const channel of channels) { - channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild); - } - channels.sort((a, b) => a.position - b.position); + for await (const channel of channels) { + channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild); + } + channels.sort((a, b) => a.position - b.position); - res.json(channels); - }, + res.json(channels); + }, ); router.post( - "/", - route({ - requestBody: "ChannelModifySchema", - permission: "MANAGE_CHANNELS", - responses: { - 201: { - body: "Channel", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel - const { guild_id } = req.params; - const body = req.body as ChannelModifySchema; + "/", + route({ + requestBody: "ChannelModifySchema", + permission: "MANAGE_CHANNELS", + responses: { + 201: { + body: "Channel", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel + const { guild_id } = req.params; + const body = req.body as ChannelModifySchema; - const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id); - channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild); + const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id); + channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild); - res.status(201).json(channel); - }, + res.status(201).json(channel); + }, ); router.patch( - "/", - route({ - requestBody: "ChannelReorderSchema", - permission: "MANAGE_CHANNELS", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // changes guild channel position - const { guild_id } = req.params; - let body = req.body as ChannelReorderSchema; + "/", + route({ + requestBody: "ChannelReorderSchema", + permission: "MANAGE_CHANNELS", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // changes guild channel position + const { guild_id } = req.params; + let body = req.body as ChannelReorderSchema; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { channel_ordering: true }, - }); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { channel_ordering: true }, + }); - body = body.sort((a, b) => { - const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0); - const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0); - return apos - bpos; - }); + body = body.sort((a, b) => { + const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0); + const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0); + return apos - bpos; + }); - // The channels not listed for this query - const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x)); + // The channels not listed for this query + const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x)); - const withParents = body.filter((x) => x.parent_id !== undefined); - const withPositions = body.filter((x) => x.position !== undefined); - // You can't do it with Promise.all or the way this is being done is super incorrect - for await (const opt of withPositions) { - const channel = await Channel.findOneOrFail({ - where: { id: opt.id }, - }); + const withParents = body.filter((x) => x.parent_id !== undefined); + const withPositions = body.filter((x) => x.position !== undefined); + // You can't do it with Promise.all or the way this is being done is super incorrect + for await (const opt of withPositions) { + const channel = await Channel.findOneOrFail({ + where: { id: opt.id }, + }); - notMentioned.splice(opt.position as number, 0, channel.id); - channel.position = notMentioned.findIndex((_) => _ === channel.id); + notMentioned.splice(opt.position as number, 0, channel.id); + channel.position = notMentioned.findIndex((_) => _ === channel.id); - await emitEvent({ - event: "CHANNEL_UPDATE", - data: channel, - channel_id: channel.id, - guild_id, - } as ChannelUpdateEvent); - } - // Due to this also being able to change the order, this needs to be done in order - // have to do the parents after the positions - for await (const opt of withParents) { - const [channel, parent] = await Promise.all([ - Channel.findOneOrFail({ - where: { id: opt.id }, - }), - opt.parent_id - ? Channel.findOneOrFail({ - where: { id: opt.parent_id }, - select: { - permission_overwrites: true, - id: true, - }, - }) - : null, - ]); + await emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id: channel.id, + guild_id, + } as ChannelUpdateEvent); + } + // Due to this also being able to change the order, this needs to be done in order + // have to do the parents after the positions + for await (const opt of withParents) { + const [channel, parent] = await Promise.all([ + Channel.findOneOrFail({ + where: { id: opt.id }, + }), + opt.parent_id + ? Channel.findOneOrFail({ + where: { id: opt.parent_id }, + select: { + permission_overwrites: true, + id: true, + }, + }) + : null, + ]); - if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites }); - if (parent && opt.position === undefined) { - const parentPos = notMentioned.indexOf(parent.id); - notMentioned.splice(parentPos + 1, 0, channel.id); - channel.position = (parentPos + 1) as number; - } - channel.parent = parent || undefined; - channel.parent_id = parent?.id || null; - await channel.save(); + if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites }); + if (parent && opt.position === undefined) { + const parentPos = notMentioned.indexOf(parent.id); + notMentioned.splice(parentPos + 1, 0, channel.id); + channel.position = (parentPos + 1) as number; + } + channel.parent = parent || undefined; + channel.parent_id = parent?.id || null; + await channel.save(); - await emitEvent({ - event: "CHANNEL_UPDATE", - data: channel, - channel_id: channel.id, - guild_id, - } as ChannelUpdateEvent); - } + await emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id: channel.id, + guild_id, + } as ChannelUpdateEvent); + } - await Guild.update({ id: guild_id }, { channel_ordering: notMentioned }); + await Guild.update({ id: guild_id }, { channel_ordering: notMentioned }); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/delete.ts" "b/src/api/routes/guilds/\043guild_id/delete.ts" index 509445c..4edd2c3 100644 --- "a/src/api/routes/guilds/\043guild_id/delete.ts" +++ "b/src/api/routes/guilds/\043guild_id/delete.ts" @@ -26,40 +26,40 @@ // discord prefixes this route with /delete instead of using the delete method // docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild router.post( - "/", - route({ - responses: { - 204: {}, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 204: {}, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: ["owner_id"], - }); - if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: ["owner_id"], + }); + if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); - await Promise.all([ - Guild.delete({ id: guild_id }), // this will also delete all guild related data - emitEvent({ - event: "GUILD_DELETE", - data: { - id: guild_id, - }, - guild_id: guild_id, - } as GuildDeleteEvent), - ]); + await Promise.all([ + Guild.delete({ id: guild_id }), // this will also delete all guild related data + emitEvent({ + event: "GUILD_DELETE", + data: { + id: guild_id, + }, + guild_id: guild_id, + } as GuildDeleteEvent), + ]); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/discovery-requirements.ts" "b/src/api/routes/guilds/\043guild_id/discovery-requirements.ts" index 8972733..150c920 100644 --- "a/src/api/routes/guilds/\043guild_id/discovery-requirements.ts" +++ "b/src/api/routes/guilds/\043guild_id/discovery-requirements.ts" @@ -22,44 +22,44 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildDiscoveryRequirementsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - // TODO: - // Load from database - // Admin control, but for now it allows anyone to be discoverable + "/", + route({ + responses: { + 200: { + body: "GuildDiscoveryRequirementsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + // TODO: + // Load from database + // Admin control, but for now it allows anyone to be discoverable - res.send({ - guild_id: guild_id, - safe_environment: true, - healthy: true, - health_score_pending: false, - size: true, - nsfw_properties: {}, - protected: true, - sufficient: true, - sufficient_without_grace_period: true, - valid_rules_channel: true, - retention_healthy: true, - engagement_healthy: true, - age: true, - minimum_age: 0, - health_score: { - avg_nonnew_participators: 0, - avg_nonnew_communicators: 0, - num_intentful_joiners: 0, - perc_ret_w1_intentful: 0, - }, - minimum_size: 0, - }); - }, + res.send({ + guild_id: guild_id, + safe_environment: true, + healthy: true, + health_score_pending: false, + size: true, + nsfw_properties: {}, + protected: true, + sufficient: true, + sufficient_without_grace_period: true, + valid_rules_channel: true, + retention_healthy: true, + engagement_healthy: true, + age: true, + minimum_age: 0, + health_score: { + avg_nonnew_participators: 0, + avg_nonnew_communicators: 0, + num_intentful_joiners: 0, + perc_ret_w1_intentful: 0, + }, + minimum_size: 0, + }); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/emojis.ts" "b/src/api/routes/guilds/\043guild_id/emojis.ts" index c74f939..5b07380 100644 --- "a/src/api/routes/guilds/\043guild_id/emojis.ts" +++ "b/src/api/routes/guilds/\043guild_id/emojis.ts" @@ -24,186 +24,186 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIEmojiArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "APIEmojiArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); - const emojis = await Emoji.find({ - where: { guild_id: guild_id }, - relations: ["user"], - }); + const emojis = await Emoji.find({ + where: { guild_id: guild_id }, + relations: ["user"], + }); - return res.json(emojis); - }, + return res.json(emojis); + }, ); router.get( - "/:emoji_id", - route({ - responses: { - 200: { - body: "Emoji", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, emoji_id } = req.params; + "/:emoji_id", + route({ + responses: { + 200: { + body: "Emoji", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, emoji_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); - const emoji = await Emoji.findOneOrFail({ - where: { guild_id: guild_id, id: emoji_id }, - relations: ["user"], - }); + const emoji = await Emoji.findOneOrFail({ + where: { guild_id: guild_id, id: emoji_id }, + relations: ["user"], + }); - return res.json(emoji); - }, + return res.json(emoji); + }, ); router.post( - "/", - route({ - requestBody: "EmojiCreateSchema", - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 201: { - body: "Emoji", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as EmojiCreateSchema; + "/", + route({ + requestBody: "EmojiCreateSchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 201: { + body: "Emoji", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as EmojiCreateSchema; - const id = Snowflake.generate(); - const emoji_count = await Emoji.count({ - where: { guild_id: guild_id }, - }); - const { maxEmojis } = Config.get().limits.guild; + const id = Snowflake.generate(); + const emoji_count = await Emoji.count({ + where: { guild_id: guild_id }, + }); + const { maxEmojis } = Config.get().limits.guild; - if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis); - if (body.require_colons == null) body.require_colons = true; + if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis); + if (body.require_colons == null) body.require_colons = true; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - await handleFile(`/emojis/${id}`, body.image); + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + await handleFile(`/emojis/${id}`, body.image); - const mimeType = body.image.split(":")[1].split(";")[0]; - const emoji = await Emoji.create({ - id: id, - guild_id: guild_id, - name: body.name, - require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not - user: user, - managed: false, - animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm", - available: true, - roles: [], - }).save(); + const mimeType = body.image.split(":")[1].split(";")[0]; + const emoji = await Emoji.create({ + id: id, + guild_id: guild_id, + name: body.name, + require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not + user: user, + managed: false, + animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm", + available: true, + roles: [], + }).save(); - await emitEvent({ - event: "GUILD_EMOJIS_UPDATE", - guild_id: guild_id, - data: { - guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }), - }, - } as GuildEmojisUpdateEvent); + await emitEvent({ + event: "GUILD_EMOJIS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, + } as GuildEmojisUpdateEvent); - return res.status(201).json(emoji); - }, + return res.status(201).json(emoji); + }, ); router.patch( - "/:emoji_id", - route({ - requestBody: "EmojiModifySchema", - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 200: { - body: "Emoji", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { emoji_id, guild_id } = req.params; - const body = req.body as EmojiModifySchema; + "/:emoji_id", + route({ + requestBody: "EmojiModifySchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 200: { + body: "Emoji", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { emoji_id, guild_id } = req.params; + const body = req.body as EmojiModifySchema; - const emoji = await Emoji.create({ - ...body, - id: emoji_id, - guild_id: guild_id, - }).save(); + const emoji = await Emoji.create({ + ...body, + id: emoji_id, + guild_id: guild_id, + }).save(); - await emitEvent({ - event: "GUILD_EMOJIS_UPDATE", - guild_id: guild_id, - data: { - guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }), - }, - } as GuildEmojisUpdateEvent); + await emitEvent({ + event: "GUILD_EMOJIS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, + } as GuildEmojisUpdateEvent); - return res.json(emoji); - }, + return res.json(emoji); + }, ); router.delete( - "/:emoji_id", - route({ - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { emoji_id, guild_id } = req.params; + "/:emoji_id", + route({ + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { emoji_id, guild_id } = req.params; - await Emoji.delete({ - id: emoji_id, - guild_id: guild_id, - }); + await Emoji.delete({ + id: emoji_id, + guild_id: guild_id, + }); - await emitEvent({ - event: "GUILD_EMOJIS_UPDATE", - guild_id: guild_id, - data: { - guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }), - }, - } as GuildEmojisUpdateEvent); + await emitEvent({ + event: "GUILD_EMOJIS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, + } as GuildEmojisUpdateEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/index.ts" "b/src/api/routes/guilds/\043guild_id/index.ts" index d6593bd..38cc913 100644 --- "a/src/api/routes/guilds/\043guild_id/index.ts" +++ "b/src/api/routes/guilds/\043guild_id/index.ts" @@ -25,188 +25,188 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - "200": { - body: "APIGuildWithJoinedAt", - }, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + "200": { + body: "APIGuildWithJoinedAt", + }, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const [guild, member] = await Promise.all([Guild.findOneOrFail({ where: { id: guild_id } }), Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })]); - if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + const [guild, member] = await Promise.all([Guild.findOneOrFail({ where: { id: guild_id } }), Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })]); + if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); - return res.send({ - ...guild, - joined_at: member?.joined_at, - }); - }, + return res.send({ + ...guild, + joined_at: member?.joined_at, + }); + }, ); router.patch( - "/", - route({ - requestBody: "GuildUpdateSchema", - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "GuildCreateResponse", - }, - 401: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as GuildUpdateSchema; - const { guild_id } = req.params; + "/", + route({ + requestBody: "GuildUpdateSchema", + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "GuildCreateResponse", + }, + 401: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as GuildUpdateSchema; + const { guild_id } = req.params; - const rights = await getRights(req.user_id); - const permission = await getPermission(req.user_id, guild_id); + const rights = await getRights(req.user_id); + const permission = await getPermission(req.user_id, guild_id); - if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS"); + if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS"); - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - relations: ["emojis", "roles", "stickers"], - }); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + relations: ["emojis", "roles", "stickers"], + }); - // trying to `select` this fails - guild.channel_ordering = ( - await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { channel_ordering: true }, - }) - ).channel_ordering; + // trying to `select` this fails + guild.channel_ordering = ( + await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { channel_ordering: true }, + }) + ).channel_ordering; - // TODO: guild update check image + // TODO: guild update check image - if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); + if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); - if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); + if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); - if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); + if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); - if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash) - body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash); + if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash) + body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash); - if (body.features) { - const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x))); + if (body.features) { + const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x))); - // TODO move these - const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"]; + // TODO move these + const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"]; - for (const feature of diff) { - if (MUTABLE_FEATURES.includes(feature)) continue; + for (const feature of diff) { + if (MUTABLE_FEATURES.includes(feature)) continue; - throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature); - } + throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature); + } - // for some reason, they don't update in the assign. - guild.features = body.features; - } + // for some reason, they don't update in the assign. + guild.features = body.features; + } - // TODO: check if body ids are valid - guild.assign(body); + // TODO: check if body ids are valid + guild.assign(body); - if (body.public_updates_channel_id == "1") { - // create an updates channel for them - const channel = await Channel.createChannel( - { - name: "moderator-only", - guild_id: guild.id, - position: 0, - type: 0, - permission_overwrites: [ - // remove SEND_MESSAGES from @everyone - { - id: guild.id, - allow: "0", - deny: Permissions.FLAGS.VIEW_CHANNEL.toString(), - type: 0, - }, - ], - }, - undefined, - { skipPermissionCheck: true }, - ); + if (body.public_updates_channel_id == "1") { + // create an updates channel for them + const channel = await Channel.createChannel( + { + name: "moderator-only", + guild_id: guild.id, + position: 0, + type: 0, + permission_overwrites: [ + // remove SEND_MESSAGES from @everyone + { + id: guild.id, + allow: "0", + deny: Permissions.FLAGS.VIEW_CHANNEL.toString(), + type: 0, + }, + ], + }, + undefined, + { skipPermissionCheck: true }, + ); - await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); + await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); - guild.public_updates_channel_id = channel.id; - } else if (body.public_updates_channel_id != undefined) { - // ensure channel exists in this guild - await Channel.findOneOrFail({ - where: { guild_id, id: body.public_updates_channel_id }, - select: { id: true }, - }); - } + guild.public_updates_channel_id = channel.id; + } else if (body.public_updates_channel_id != undefined) { + // ensure channel exists in this guild + await Channel.findOneOrFail({ + where: { guild_id, id: body.public_updates_channel_id }, + select: { id: true }, + }); + } - if (body.rules_channel_id == "1") { - // create a rules for them - const channel = await Channel.createChannel( - { - name: "rules", - guild_id: guild.id, - position: 0, - type: 0, - permission_overwrites: [ - // remove SEND_MESSAGES from @everyone - { - id: guild.id, - allow: "0", - deny: Permissions.FLAGS.SEND_MESSAGES.toString(), - type: 0, - }, - ], - }, - undefined, - { skipPermissionCheck: true }, - ); + if (body.rules_channel_id == "1") { + // create a rules for them + const channel = await Channel.createChannel( + { + name: "rules", + guild_id: guild.id, + position: 0, + type: 0, + permission_overwrites: [ + // remove SEND_MESSAGES from @everyone + { + id: guild.id, + allow: "0", + deny: Permissions.FLAGS.SEND_MESSAGES.toString(), + type: 0, + }, + ], + }, + undefined, + { skipPermissionCheck: true }, + ); - await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); + await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); - guild.rules_channel_id = channel.id; - } else if (body.rules_channel_id != undefined) { - // ensure channel exists in this guild - await Channel.findOneOrFail({ - where: { guild_id, id: body.rules_channel_id }, - select: { id: true }, - }); - } + guild.rules_channel_id = channel.id; + } else if (body.rules_channel_id != undefined) { + // ensure channel exists in this guild + await Channel.findOneOrFail({ + where: { guild_id, id: body.rules_channel_id }, + select: { id: true }, + }); + } - const data = guild.toJSON(); - // TODO: guild hashes - // TODO: fix vanity_url_code, template_id - // delete data.vanity_url_code; - delete data.template_id; + const data = guild.toJSON(); + // TODO: guild hashes + // TODO: fix vanity_url_code, template_id + // delete data.vanity_url_code; + delete data.template_id; - await Promise.all([ - guild.save(), - emitEvent({ - event: "GUILD_UPDATE", - data, - guild_id, - } as GuildUpdateEvent), - ]); + await Promise.all([ + guild.save(), + emitEvent({ + event: "GUILD_UPDATE", + data, + guild_id, + } as GuildUpdateEvent), + ]); - return res.json(data); - }, + return res.json(data); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/integrations.ts" "b/src/api/routes/guilds/\043guild_id/integrations.ts" index e396e23..704b5a5 100644 --- "a/src/api/routes/guilds/\043guild_id/integrations.ts" +++ "b/src/api/routes/guilds/\043guild_id/integrations.ts" @@ -22,6 +22,6 @@ //TODO: implement integrations list router.get("/", route({}), async (req: Request, res: Response) => { - res.json([]); + res.json([]); }); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/invites.ts" "b/src/api/routes/guilds/\043guild_id/invites.ts" index 9372651..32d86a4 100644 --- "a/src/api/routes/guilds/\043guild_id/invites.ts" +++ "b/src/api/routes/guilds/\043guild_id/invites.ts" @@ -23,33 +23,33 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "APIInviteArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "APIInviteArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const invites = await Invite.find({ - where: { guild_id }, - relations: PublicInviteRelation, - }); + const invites = await Invite.find({ + where: { guild_id }, + relations: PublicInviteRelation, + }); - await Promise.all( - invites - .filter((i) => i.isExpired()) - .map(async (i) => { - await Invite.delete({ code: i.code }); - }), - ); + await Promise.all( + invites + .filter((i) => i.isExpired()) + .map(async (i) => { + await Invite.delete({ code: i.code }); + }), + ); - return res.json(invites.filter((i) => !i.isExpired())); - }, + return res.json(invites.filter((i) => !i.isExpired())); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/member-verification.ts" "b/src/api/routes/guilds/\043guild_id/member-verification.ts" index 20225e2..11b36a3 100644 --- "a/src/api/routes/guilds/\043guild_id/member-verification.ts" +++ "b/src/api/routes/guilds/\043guild_id/member-verification.ts" @@ -21,22 +21,22 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: member verification + "/", + route({ + responses: { + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: member verification - res.status(404).json({ - message: "Unknown Guild Member Verification Form", - code: 10068, - }); - }, + res.status(404).json({ + message: "Unknown Guild Member Verification Form", + code: 10068, + }); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/members/\043member_id/index.ts" "b/src/api/routes/guilds/\043guild_id/members/\043member_id/index.ts" index 9696cb6..81cda18 100644 --- "a/src/api/routes/guilds/\043guild_id/members/\043member_id/index.ts" +++ "b/src/api/routes/guilds/\043guild_id/members/\043member_id/index.ts" @@ -24,212 +24,212 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIPublicMember", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, member_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + "/", + route({ + responses: { + 200: { + body: "APIPublicMember", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, member_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); - const member = await Member.findOneOrFail({ - where: { id: member_id, guild_id }, - relations: ["roles", "user"], - select: { - index: true, - // only grab public member props - ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])), - // and public user props - user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), - roles: { - id: true, - }, - }, - }); + const member = await Member.findOneOrFail({ + where: { id: member_id, guild_id }, + relations: ["roles", "user"], + select: { + index: true, + // only grab public member props + ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])), + // and public user props + user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), + roles: { + id: true, + }, + }, + }); - return res.json({ - ...member.toPublicMember(), - user: member.user.toPublicUser(), - roles: member.roles.map((x) => x.id), - }); - }, + return res.json({ + ...member.toPublicMember(), + user: member.user.toPublicUser(), + roles: member.roles.map((x) => x.id), + }); + }, ); router.patch( - "/", - route({ - requestBody: "MemberChangeSchema", - responses: { - 200: { - body: "Member", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const member_id = req.params.member_id === "@me" ? req.user_id : req.params.member_id; - const body = req.body as MemberChangeSchema; + "/", + route({ + requestBody: "MemberChangeSchema", + responses: { + 200: { + body: "Member", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const member_id = req.params.member_id === "@me" ? req.user_id : req.params.member_id; + const body = req.body as MemberChangeSchema; - const member = await Member.findOneOrFail({ - where: { id: member_id, guild_id }, - relations: ["roles", "user"], - }); - const permission = await getPermission(req.user_id, guild_id); + const member = await Member.findOneOrFail({ + where: { id: member_id, guild_id }, + relations: ["roles", "user"], + }); + const permission = await getPermission(req.user_id, guild_id); - if ("nick" in body) { - permission.hasThrow("MANAGE_NICKNAMES"); + if ("nick" in body) { + permission.hasThrow("MANAGE_NICKNAMES"); - if (!body.nick) { - delete body.nick; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore shut up - member.nick = null; // remove the nickname - } - } + if (!body.nick) { + delete body.nick; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore shut up + member.nick = null; // remove the nickname + } + } - if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") { - const rights = await getRights(req.user_id); - rights.hasThrow("MANAGE_USERS"); - } + if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") { + const rights = await getRights(req.user_id); + rights.hasThrow("MANAGE_USERS"); + } - if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string); + if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string); - member.assign(body); + member.assign(body); - // must do this after the assign because the body roles array - // is string[] not Role[] - if ("roles" in body) { - permission.hasThrow("MANAGE_ROLES"); + // must do this after the assign because the body roles array + // is string[] not Role[] + if ("roles" in body) { + permission.hasThrow("MANAGE_ROLES"); - body.roles = body.roles || []; - body.roles.filter((x) => !!x); + body.roles = body.roles || []; + body.roles.filter((x) => !!x); - if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id); - // foreign key constraint will fail if role doesn't exist - member.roles = body.roles.map((x) => Role.create({ id: x })); - } + if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id); + // foreign key constraint will fail if role doesn't exist + member.roles = body.roles.map((x) => Role.create({ id: x })); + } - if ("communication_disabled_until" in body) { - permission.hasThrow("MODERATE_MEMBERS"); - member.communication_disabled_until = body.communication_disabled_until == null ? null : new Date(body.communication_disabled_until); - } + if ("communication_disabled_until" in body) { + permission.hasThrow("MODERATE_MEMBERS"); + member.communication_disabled_until = body.communication_disabled_until == null ? null : new Date(body.communication_disabled_until); + } - await member.save(); + await member.save(); - member.roles = member.roles.filter((x) => x.id !== guild_id); + member.roles = member.roles.filter((x) => x.id !== guild_id); - // do not use promise.all as we have to first write to db before emitting the event to catch errors - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: { ...member, roles: member.roles.map((x) => x.id) }, - } as GuildMemberUpdateEvent); + // do not use promise.all as we have to first write to db before emitting the event to catch errors + await emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.roles.map((x) => x.id) }, + } as GuildMemberUpdateEvent); - res.json(member); - }, + res.json(member); + }, ); router.put( - "/", - route({ - responses: { - 200: { - body: "MemberJoinGuildResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: Lurker mode + "/", + route({ + responses: { + 200: { + body: "MemberJoinGuildResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Lurker mode - const rights = await getRights(req.user_id); + const rights = await getRights(req.user_id); - const { guild_id } = req.params; - let { member_id } = req.params; - if (member_id === "@me") { - member_id = req.user_id; - rights.hasThrow("JOIN_GUILDS"); - if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT; - } else { - // TODO: check oauth2 scope + const { guild_id } = req.params; + let { member_id } = req.params; + if (member_id === "@me") { + member_id = req.user_id; + rights.hasThrow("JOIN_GUILDS"); + if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT; + } else { + // TODO: check oauth2 scope - throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE; - } + throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE; + } - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - }); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + }); - if (!guild.features.includes("DISCOVERABLE")) { - throw DiscordApiErrors.UNKNOWN_GUILD; - } + if (!guild.features.includes("DISCOVERABLE")) { + throw DiscordApiErrors.UNKNOWN_GUILD; + } - const emoji = await Emoji.find({ - where: { guild_id: guild_id }, - }); + const emoji = await Emoji.find({ + where: { guild_id: guild_id }, + }); - const roles = await Role.find({ - where: { guild_id: guild_id }, - }); + const roles = await Role.find({ + where: { guild_id: guild_id }, + }); - const stickers = await Sticker.find({ - where: { guild_id: guild_id }, - }); + const stickers = await Sticker.find({ + where: { guild_id: guild_id }, + }); - await Member.addToGuild(member_id, guild_id); - res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers }); - }, + await Member.addToGuild(member_id, guild_id); + res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers }); + }, ); router.delete( - "/", - route({ - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, member_id } = req.params; - const permission = await getPermission(req.user_id, guild_id); - const rights = await getRights(req.user_id); - if (member_id === "@me" || member_id === req.user_id) { - // TODO: unless force-joined - rights.hasThrow("SELF_LEAVE_GROUPS"); - } else { - rights.hasThrow("KICK_BAN_MEMBERS"); - permission.hasThrow("KICK_MEMBERS"); - } + "/", + route({ + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, member_id } = req.params; + const permission = await getPermission(req.user_id, guild_id); + const rights = await getRights(req.user_id); + if (member_id === "@me" || member_id === req.user_id) { + // TODO: unless force-joined + rights.hasThrow("SELF_LEAVE_GROUPS"); + } else { + rights.hasThrow("KICK_BAN_MEMBERS"); + permission.hasThrow("KICK_MEMBERS"); + } - await Member.removeFromGuild(member_id, guild_id); - res.sendStatus(204); - }, + await Member.removeFromGuild(member_id, guild_id); + res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/members/\043member_id/nick.ts" "b/src/api/routes/guilds/\043guild_id/members/\043member_id/nick.ts" index 2a70777..33f8915 100644 --- "a/src/api/routes/guilds/\043guild_id/members/\043member_id/nick.ts" +++ "b/src/api/routes/guilds/\043guild_id/members/\043member_id/nick.ts" @@ -23,38 +23,38 @@ const router = Router({ mergeParams: true }); router.patch( - "/", - route({ - requestBody: "MemberNickChangeSchema", - responses: { - 200: { - body: "APIPublicMember", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - let permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; - const member_id = req.params.member_id === "@me" ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) : req.params.member_id; + "/", + route({ + requestBody: "MemberNickChangeSchema", + responses: { + 200: { + body: "APIPublicMember", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + let permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; + const member_id = req.params.member_id === "@me" ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) : req.params.member_id; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow(permissionString); + const perms = await getPermission(req.user_id, guild_id); + perms.hasThrow(permissionString); - await Member.changeNickname(member_id, guild_id, req.body.nick); + await Member.changeNickname(member_id, guild_id, req.body.nick); - const member = await Member.findOne({ - where: { id: member_id, guild_id }, - relations: ["roles"], - }); + const member = await Member.findOne({ + where: { id: member_id, guild_id }, + relations: ["roles"], + }); - res.send(member?.toPublicMember()); - }, + res.send(member?.toPublicMember()); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/members/\043member_id/roles/\043role_id/index.ts" "b/src/api/routes/guilds/\043guild_id/members/\043member_id/roles/\043role_id/index.ts" index 5a5bfdf..d482c06 100644 --- "a/src/api/routes/guilds/\043guild_id/members/\043member_id/roles/\043role_id/index.ts" +++ "b/src/api/routes/guilds/\043guild_id/members/\043member_id/roles/\043role_id/index.ts" @@ -23,39 +23,39 @@ const router = Router({ mergeParams: true }); router.delete( - "/", - route({ - permission: "MANAGE_ROLES", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, role_id, member_id } = req.params; + "/", + route({ + permission: "MANAGE_ROLES", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, role_id, member_id } = req.params; - await Member.removeRole(member_id, guild_id, role_id); - res.sendStatus(204); - }, + await Member.removeRole(member_id, guild_id, role_id); + res.sendStatus(204); + }, ); router.put( - "/", - route({ - permission: "MANAGE_ROLES", - responses: { - 204: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, role_id, member_id } = req.params; + "/", + route({ + permission: "MANAGE_ROLES", + responses: { + 204: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, role_id, member_id } = req.params; - await Member.addRole(member_id, guild_id, role_id); - res.sendStatus(204); - }, + await Member.addRole(member_id, guild_id, role_id); + res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/members/index.ts" "b/src/api/routes/guilds/\043guild_id/members/index.ts" index a047472..08b7707 100644 --- "a/src/api/routes/guilds/\043guild_id/members/index.ts" +++ "b/src/api/routes/guilds/\043guild_id/members/index.ts" @@ -29,44 +29,44 @@ // TODO: check for GUILD_MEMBERS intent router.get( - "/", - route({ - query: { - limit: { - type: "number", - description: "max number of members to return (1-1000). default 1", - }, - after: { - type: "string", - }, - }, - responses: { - 200: { - body: "APIMemberArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const limit = Number(req.query.limit) || 1; - if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000"); - const after = `${req.query.after}`; - const query = after ? { id: MoreThan(after) } : {}; + "/", + route({ + query: { + limit: { + type: "number", + description: "max number of members to return (1-1000). default 1", + }, + after: { + type: "string", + }, + }, + responses: { + 200: { + body: "APIMemberArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const limit = Number(req.query.limit) || 1; + if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000"); + const after = `${req.query.after}`; + const query = after ? { id: MoreThan(after) } : {}; - await Member.IsInGuildOrFail(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); - const members = await Member.find({ - where: { guild_id, ...query }, - select: PublicMemberProjection, - take: limit, - order: { id: "ASC" }, - }); + const members = await Member.find({ + where: { guild_id, ...query }, + select: PublicMemberProjection, + take: limit, + order: { id: "ASC" }, + }); - return res.json(members); - }, + return res.json(members); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/messages/search.ts" "b/src/api/routes/guilds/\043guild_id/messages/search.ts" index 6532b53..be00d5d 100644 --- "a/src/api/routes/guilds/\043guild_id/messages/search.ts" +++ "b/src/api/routes/guilds/\043guild_id/messages/search.ts" @@ -27,125 +27,125 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildMessagesSearchResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 422: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { - channel_id, - content, - // include_nsfw, // TODO - offset, - sort_order, - // sort_by, // TODO: Handle 'relevance' - limit, - author_id, - } = req.query; + "/", + route({ + responses: { + 200: { + body: "GuildMessagesSearchResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 422: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { + channel_id, + content, + // include_nsfw, // TODO + offset, + sort_order, + // sort_by, // TODO: Handle 'relevance' + limit, + author_id, + } = req.query; - const parsedLimit = Number(limit) || 50; - if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); + const parsedLimit = Number(limit) || 50; + if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); - if (sort_order) { - if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1) - throw FieldErrors({ - sort_order: { - message: "Value must be one of ('desc', 'asc').", - code: "BASE_TYPE_CHOICES", - }, - }); // todo this is wrong - } + if (sort_order) { + if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1) + throw FieldErrors({ + sort_order: { + message: "Value must be one of ('desc', 'asc').", + code: "BASE_TYPE_CHOICES", + }, + }); // todo this is wrong + } - const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string | undefined); - permissions.hasThrow("VIEW_CHANNEL"); - if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); + const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string | undefined); + permissions.hasThrow("VIEW_CHANNEL"); + if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); - const query: FindManyOptions = { - order: { - timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC", - }, - take: parsedLimit || 0, - where: { - guild: { - id: req.params.guild_id, - }, - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], - skip: offset ? Number(offset) : 0, - }; - //@ts-ignore - if (channel_id) query.where.channel = { id: channel_id }; - else { - // get all channel IDs that this user can access - const channels = await Channel.find({ - where: { guild_id: req.params.guild_id }, - select: ["id"], - }); - const ids = []; + const query: FindManyOptions = { + order: { + timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC", + }, + take: parsedLimit || 0, + where: { + guild: { + id: req.params.guild_id, + }, + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], + skip: offset ? Number(offset) : 0, + }; + //@ts-ignore + if (channel_id) query.where.channel = { id: channel_id }; + else { + // get all channel IDs that this user can access + const channels = await Channel.find({ + where: { guild_id: req.params.guild_id }, + select: ["id"], + }); + const ids = []; - for (const channel of channels) { - const perm = await getPermission(req.user_id, req.params.guild_id, channel.id); - if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue; - ids.push(channel.id); - } + for (const channel of channels) { + const perm = await getPermission(req.user_id, req.params.guild_id, channel.id); + if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue; + ids.push(channel.id); + } - //@ts-ignore - query.where.channel = { id: In(ids) }; - } - //@ts-ignore - if (author_id) query.where.author = { id: author_id }; - //@ts-ignore - if (content) query.where.content = Like(`%${content}%`); + //@ts-ignore + query.where.channel = { id: In(ids) }; + } + //@ts-ignore + if (author_id) query.where.author = { id: author_id }; + //@ts-ignore + if (content) query.where.content = Like(`%${content}%`); - const messages: Message[] = await Message.find(query); - delete query.take; - const total_results = await Message.count(query); + const messages: Message[] = await Message.find(query); + delete query.take; + const total_results = await Message.count(query); - const messagesDto = messages.map((x) => [ - { - id: x.id, - type: x.type, - content: x.content, - channel_id: x.channel_id, - author: { - id: x.author?.id, - username: x.author?.username, - avatar: x.author?.avatar, - avatar_decoration: null, - discriminator: x.author?.discriminator, - public_flags: x.author?.public_flags, - }, - attachments: x.attachments, - embeds: x.embeds, - mentions: x.mentions, - mention_roles: x.mention_roles, - pinned: x.pinned, - mention_everyone: x.mention_everyone, - tts: x.tts, - timestamp: x.timestamp, - edited_timestamp: x.edited_timestamp, - flags: x.flags, - components: x.components, - poll: x.poll, - hit: true, - }, - ]); + const messagesDto = messages.map((x) => [ + { + id: x.id, + type: x.type, + content: x.content, + channel_id: x.channel_id, + author: { + id: x.author?.id, + username: x.author?.username, + avatar: x.author?.avatar, + avatar_decoration: null, + discriminator: x.author?.discriminator, + public_flags: x.author?.public_flags, + }, + attachments: x.attachments, + embeds: x.embeds, + mentions: x.mentions, + mention_roles: x.mention_roles, + pinned: x.pinned, + mention_everyone: x.mention_everyone, + tts: x.tts, + timestamp: x.timestamp, + edited_timestamp: x.edited_timestamp, + flags: x.flags, + components: x.components, + poll: x.poll, + hit: true, + }, + ]); - return res.json({ - messages: messagesDto, - total_results, - }); - }, + return res.json({ + messages: messagesDto, + total_results, + }); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/premium.ts" "b/src/api/routes/guilds/\043guild_id/premium.ts" index b72ded2..fc0d34b 100644 --- "a/src/api/routes/guilds/\043guild_id/premium.ts" +++ "b/src/api/routes/guilds/\043guild_id/premium.ts" @@ -21,8 +21,8 @@ const router = Router({ mergeParams: true }); router.get("/subscriptions", route({}), async (req: Request, res: Response) => { - // TODO: - res.json([]); + // TODO: + res.json([]); }); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/profile.ts" "b/src/api/routes/guilds/\043guild_id/profile.ts" index 4b263fb..176c612 100644 --- "a/src/api/routes/guilds/\043guild_id/profile.ts" +++ "b/src/api/routes/guilds/\043guild_id/profile.ts" @@ -24,43 +24,43 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - "200": { - body: "GuildProfileResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - const profileResponse: GuildProfileResponse = { - id: guild_id, - name: guild.name, - icon_hash: guild.icon ?? null, - member_count: guild.member_count!, - online_count: guild.member_count!, - description: guild.description ?? "A Spacebar guild", - brand_color_primary: "#FF00FF", - banner_hash: null, - game_application_ids: [], // We don't track this - game_activity: {}, // We don't track this - tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags - badge: 0, - badge_color_primary: "#FF00FF", - badge_color_secondary: "#00FFFF", - badge_hash: "", - traits: [], - features: guild.features ?? [], - visibility: GuildVisibilityLevel.PUBLIC, - custom_banner_hash: guild.banner ?? null, - premium_subscription_count: guild.premium_subscription_count ?? 0, - premium_tier: guild.premium_tier ?? 0, - }; + "/", + route({ + responses: { + "200": { + body: "GuildProfileResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const profileResponse: GuildProfileResponse = { + id: guild_id, + name: guild.name, + icon_hash: guild.icon ?? null, + member_count: guild.member_count!, + online_count: guild.member_count!, + description: guild.description ?? "A Spacebar guild", + brand_color_primary: "#FF00FF", + banner_hash: null, + game_application_ids: [], // We don't track this + game_activity: {}, // We don't track this + tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags + badge: 0, + badge_color_primary: "#FF00FF", + badge_color_secondary: "#00FFFF", + badge_hash: "", + traits: [], + features: guild.features ?? [], + visibility: GuildVisibilityLevel.PUBLIC, + custom_banner_hash: guild.banner ?? null, + premium_subscription_count: guild.premium_subscription_count ?? 0, + premium_tier: guild.premium_tier ?? 0, + }; - res.send(profileResponse); - }, + res.send(profileResponse); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/profile/index.ts" "b/src/api/routes/guilds/\043guild_id/profile/index.ts" index 8f0e385..7a944b1 100644 --- "a/src/api/routes/guilds/\043guild_id/profile/index.ts" +++ "b/src/api/routes/guilds/\043guild_id/profile/index.ts" @@ -24,47 +24,47 @@ const router = Router({ mergeParams: true }); router.patch( - "/:member_id", - route({ - requestBody: "MemberChangeProfileSchema", - responses: { - 200: { - body: "Member", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - // const member_id = - // req.params.member_id === "@me" ? req.user_id : req.params.member_id; - const body = req.body as MemberChangeProfileSchema; + "/:member_id", + route({ + requestBody: "MemberChangeProfileSchema", + responses: { + 200: { + body: "Member", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + // const member_id = + // req.params.member_id === "@me" ? req.user_id : req.params.member_id; + const body = req.body as MemberChangeProfileSchema; - let member = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id }, - relations: ["roles", "user"], - }); + let member = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id }, + relations: ["roles", "user"], + }); - if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string); + if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string); - member = await OrmUtils.mergeDeep(member, body); + member = await OrmUtils.mergeDeep(member, body); - await member.save(); + await member.save(); - // do not use promise.all as we have to first write to db before emitting the event to catch errors - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: { ...member, roles: member.roles.map((x) => x.id) }, - } as GuildMemberUpdateEvent); + // do not use promise.all as we have to first write to db before emitting the event to catch errors + await emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.roles.map((x) => x.id) }, + } as GuildMemberUpdateEvent); - res.json(member); - }, + res.json(member); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/prune.ts" "b/src/api/routes/guilds/\043guild_id/prune.ts" index 47984e0..c8a42b8 100644 --- "a/src/api/routes/guilds/\043guild_id/prune.ts" +++ "b/src/api/routes/guilds/\043guild_id/prune.ts" @@ -24,102 +24,102 @@ //Returns all inactive members, respecting role hierarchy const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => { - const date = new Date(); - date.setDate(date.getDate() - days); - //Snowflake should have `generateFromTime` method? Or similar? - const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22); + const date = new Date(); + date.setDate(date.getDate() - days); + //Snowflake should have `generateFromTime` method? Or similar? + const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22); - /** + /** idea: ability to customise the cutoff variable possible candidates: public read receipt, last presence, last VC leave **/ - let members = await Member.find({ - where: [ - { - guild_id, - last_message_id: LessThan(minId.toString()), - }, - { - guild_id, - last_message_id: IsNull(), - }, - ], - relations: ["roles"], - }); - if (!members.length) return []; + let members = await Member.find({ + where: [ + { + guild_id, + last_message_id: LessThan(minId.toString()), + }, + { + guild_id, + last_message_id: IsNull(), + }, + ], + relations: ["roles"], + }); + if (!members.length) return []; - //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well. - if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id))); + //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well. + if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id))); - const me = await Member.findOneOrFail({ - where: { id: user_id, guild_id }, - relations: ["roles"], - }); - const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || [])); + const me = await Member.findOneOrFail({ + where: { id: user_id, guild_id }, + relations: ["roles"], + }); + const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || [])); - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - members = members.filter( - (member) => - member.id !== guild.owner_id && //can't kick owner - member.roles?.some( - (role) => - role.position < myHighestRole || //roles higher than me can't be kicked - me.id === guild.owner_id, //owner can kick anyone - ), - ); + members = members.filter( + (member) => + member.id !== guild.owner_id && //can't kick owner + member.roles?.some( + (role) => + role.position < myHighestRole || //roles higher than me can't be kicked + me.id === guild.owner_id, //owner can kick anyone + ), + ); - return members; + return members; }; router.get( - "/", - route({ - responses: { - "200": { - body: "GuildPruneResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const days = parseInt(req.query.days as string); + "/", + route({ + responses: { + "200": { + body: "GuildPruneResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const days = parseInt(req.query.days as string); - let roles = req.query.include_roles; - if (typeof roles === "string") roles = [roles]; //express will return array otherwise + let roles = req.query.include_roles; + if (typeof roles === "string") roles = [roles]; //express will return array otherwise - const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]); + const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]); - res.send({ pruned: members.length }); - }, + res.send({ pruned: members.length }); + }, ); router.post( - "/", - route({ - permission: "KICK_MEMBERS", - right: "KICK_BAN_MEMBERS", - responses: { - 200: { - body: "GuildPurgeResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const days = parseInt(req.body.days); + "/", + route({ + permission: "KICK_MEMBERS", + right: "KICK_BAN_MEMBERS", + responses: { + 200: { + body: "GuildPurgeResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const days = parseInt(req.body.days); - let roles = req.query.include_roles; - if (typeof roles === "string") roles = [roles]; + let roles = req.query.include_roles; + if (typeof roles === "string") roles = [roles]; - const { guild_id } = req.params; - const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]); + const { guild_id } = req.params; + const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]); - await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id))); + await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id))); - res.send({ purged: members.length }); - }, + res.send({ purged: members.length }); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/regions.ts" "b/src/api/routes/guilds/\043guild_id/regions.ts" index a3c5a81..b2a311d 100644 --- "a/src/api/routes/guilds/\043guild_id/regions.ts" +++ "b/src/api/routes/guilds/\043guild_id/regions.ts" @@ -23,23 +23,23 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIGuildVoiceRegion", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - //TODO we should use an enum for guild's features and not hardcoded strings - return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS"))); - }, + "/", + route({ + responses: { + 200: { + body: "APIGuildVoiceRegion", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + //TODO we should use an enum for guild's features and not hardcoded strings + return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS"))); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/roles/\043role_id/index.ts" "b/src/api/routes/guilds/\043guild_id/roles/\043role_id/index.ts" index b8349eb..1811284 100644 --- "a/src/api/routes/guilds/\043guild_id/roles/\043role_id/index.ts" +++ "b/src/api/routes/guilds/\043guild_id/roles/\043role_id/index.ts" @@ -25,121 +25,121 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Role", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, role_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); - const role = await Role.findOneOrFail({ - where: { guild_id, id: role_id }, - }); - return res.json(role); - }, + "/", + route({ + responses: { + 200: { + body: "Role", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, role_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); + const role = await Role.findOneOrFail({ + where: { guild_id, id: role_id }, + }); + return res.json(role); + }, ); router.delete( - "/", - route({ - permission: "MANAGE_ROLES", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, role_id } = req.params; - if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); + "/", + route({ + permission: "MANAGE_ROLES", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, role_id } = req.params; + if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); - await Promise.all([ - Role.delete({ - id: role_id, - guild_id: guild_id, - }), - emitEvent({ - event: "GUILD_ROLE_DELETE", - guild_id, - data: { - guild_id, - role_id, - }, - } as GuildRoleDeleteEvent), - ]); + await Promise.all([ + Role.delete({ + id: role_id, + guild_id: guild_id, + }), + emitEvent({ + event: "GUILD_ROLE_DELETE", + guild_id, + data: { + guild_id, + role_id, + }, + } as GuildRoleDeleteEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); // TODO: check role hierarchy router.patch( - "/", - route({ - requestBody: "RoleModifySchema", - permission: "MANAGE_ROLES", - responses: { - 200: { - body: "Role", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { role_id, guild_id } = req.params; - const body = req.body as RoleModifySchema; + "/", + route({ + requestBody: "RoleModifySchema", + permission: "MANAGE_ROLES", + responses: { + 200: { + body: "Role", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { role_id, guild_id } = req.params; + const body = req.body as RoleModifySchema; - if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); - else body.icon = undefined; + if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); + else body.icon = undefined; - const role = await Role.findOneOrFail({ - where: { id: role_id, guild: { id: guild_id } }, - }); - role.assign({ - ...body, - permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")), - }); + const role = await Role.findOneOrFail({ + where: { id: role_id, guild: { id: guild_id } }, + }); + role.assign({ + ...body, + permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")), + }); - await Promise.all([ - role.save(), - emitEvent({ - event: "GUILD_ROLE_UPDATE", - guild_id, - data: { - guild_id, - role, - }, - } as GuildRoleUpdateEvent), - ]); + await Promise.all([ + role.save(), + emitEvent({ + event: "GUILD_ROLE_UPDATE", + guild_id, + data: { + guild_id, + role, + }, + } as GuildRoleUpdateEvent), + ]); - res.json(role); - }, + res.json(role); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/roles/\043role_id/member-ids.ts" "b/src/api/routes/guilds/\043guild_id/roles/\043role_id/member-ids.ts" index 6558da5..ab61325 100644 --- "a/src/api/routes/guilds/\043guild_id/roles/\043role_id/member-ids.ts" +++ "b/src/api/routes/guilds/\043guild_id/roles/\043role_id/member-ids.ts" @@ -23,20 +23,20 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id, role_id } = req.params; + const { guild_id, role_id } = req.params; - // TODO: Is this route really not paginated? - const members = await Member.find({ - select: ["id"], - where: { - roles: { - id: role_id, - }, - guild_id, - }, - }); + // TODO: Is this route really not paginated? + const members = await Member.find({ + select: ["id"], + where: { + roles: { + id: role_id, + }, + guild_id, + }, + }); - return res.json(members.map((x) => x.id)); + return res.json(members.map((x) => x.id)); }); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/roles/\043role_id/members.ts" "b/src/api/routes/guilds/\043guild_id/roles/\043role_id/members.ts" index d22d143..f89d593 100644 --- "a/src/api/routes/guilds/\043guild_id/roles/\043role_id/members.ts" +++ "b/src/api/routes/guilds/\043guild_id/roles/\043role_id/members.ts" @@ -23,24 +23,24 @@ const router = Router({ mergeParams: true }); router.patch("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - // Payload is JSON containing a list of member_ids, the new list of members to have the role - const { guild_id, role_id } = req.params; - const { member_ids } = req.body; + // Payload is JSON containing a list of member_ids, the new list of members to have the role + const { guild_id, role_id } = req.params; + const { member_ids } = req.body; - // don't mess with @everyone - if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE; + // don't mess with @everyone + if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE; - const members = await Member.find({ - where: { guild_id }, - relations: ["roles"], - }); + const members = await Member.find({ + where: { guild_id }, + relations: ["roles"], + }); - const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id)); + const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id)); - // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn - await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]); + // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn + await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]); - res.sendStatus(204); + res.sendStatus(204); }); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/roles/index.ts" "b/src/api/routes/guilds/\043guild_id/roles/index.ts" index 26584e9..8caea56 100644 --- "a/src/api/routes/guilds/\043guild_id/roles/index.ts" +++ "b/src/api/routes/guilds/\043guild_id/roles/index.ts" @@ -25,129 +25,129 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; + const guild_id = req.params.guild_id; - await Member.IsInGuildOrFail(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); - const roles = await Role.find({ where: { guild_id: guild_id } }); + const roles = await Role.find({ where: { guild_id: guild_id } }); - return res.json(roles); + return res.json(roles); }); router.post( - "/", - route({ - requestBody: "RoleModifySchema", - permission: "MANAGE_ROLES", - responses: { - 200: { - body: "Role", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const body = req.body as RoleModifySchema; + "/", + route({ + requestBody: "RoleModifySchema", + permission: "MANAGE_ROLES", + responses: { + 200: { + body: "Role", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const guild_id = req.params.guild_id; + const body = req.body as RoleModifySchema; - const role_count = await Role.count({ where: { guild_id } }); - const { maxRoles } = Config.get().limits.guild; + const role_count = await Role.count({ where: { guild_id } }); + const { maxRoles } = Config.get().limits.guild; - if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); + if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); - const role = Role.create({ - // values before ...body are default and can be overridden - position: 1, - hoist: false, - color: 0, - mentionable: false, - ...body, - guild_id: guild_id, - managed: false, - permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")), - tags: undefined, - icon: undefined, - unicode_emoji: undefined, - id: Snowflake.generate(), - colors: { - primary_color: body.colors?.primary_color || body.color || 0, - secondary_color: body.colors?.secondary_color || undefined, // gradient - tertiary_color: body.colors?.tertiary_color || undefined, // "holographic" - }, - }); + const role = Role.create({ + // values before ...body are default and can be overridden + position: 1, + hoist: false, + color: 0, + mentionable: false, + ...body, + guild_id: guild_id, + managed: false, + permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")), + tags: undefined, + icon: undefined, + unicode_emoji: undefined, + id: Snowflake.generate(), + colors: { + primary_color: body.colors?.primary_color || body.color || 0, + secondary_color: body.colors?.secondary_color || undefined, // gradient + tertiary_color: body.colors?.tertiary_color || undefined, // "holographic" + }, + }); - await Promise.all([ - role.save(), - // Move all existing roles up one position, to accommodate the new role - Role.createQueryBuilder("roles") - .where({ - guild: { id: guild_id }, - name: Not("@everyone"), - id: Not(role.id), - }) - .update({ position: () => "position + 1" }) - .execute(), - emitEvent({ - event: "GUILD_ROLE_CREATE", - guild_id, - data: { - guild_id, - role: role, - }, - } as GuildRoleCreateEvent), - ]); + await Promise.all([ + role.save(), + // Move all existing roles up one position, to accommodate the new role + Role.createQueryBuilder("roles") + .where({ + guild: { id: guild_id }, + name: Not("@everyone"), + id: Not(role.id), + }) + .update({ position: () => "position + 1" }) + .execute(), + emitEvent({ + event: "GUILD_ROLE_CREATE", + guild_id, + data: { + guild_id, + role: role, + }, + } as GuildRoleCreateEvent), + ]); - res.json(role); - }, + res.json(role); + }, ); router.patch( - "/", - route({ - requestBody: "RolePositionUpdateSchema", - permission: "MANAGE_ROLES", - responses: { - 200: { - body: "APIRoleArray", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as RolePositionUpdateSchema; + "/", + route({ + requestBody: "RolePositionUpdateSchema", + permission: "MANAGE_ROLES", + responses: { + 200: { + body: "APIRoleArray", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as RolePositionUpdateSchema; - await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); + await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); - const roles = await Role.find({ - where: body.map((x) => ({ id: x.id, guild_id })), - }); + const roles = await Role.find({ + where: body.map((x) => ({ id: x.id, guild_id })), + }); - await Promise.all( - roles.map((x) => - emitEvent({ - event: "GUILD_ROLE_UPDATE", - guild_id, - data: { - guild_id, - role: x, - }, - } as GuildRoleUpdateEvent), - ), - ); + await Promise.all( + roles.map((x) => + emitEvent({ + event: "GUILD_ROLE_UPDATE", + guild_id, + data: { + guild_id, + role: x, + }, + } as GuildRoleUpdateEvent), + ), + ); - res.json(roles); - }, + res.json(roles); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/roles/member-counts.ts" "b/src/api/routes/guilds/\043guild_id/roles/member-counts.ts" index 5574e59..d77149d 100644 --- "a/src/api/routes/guilds/\043guild_id/roles/member-counts.ts" +++ "b/src/api/routes/guilds/\043guild_id/roles/member-counts.ts" @@ -23,16 +23,16 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + const { guild_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); - const role_ids = await Role.find({ where: { guild_id }, select: ["id"] }); - const counts: { [id: string]: number } = {}; - for (const { id } of role_ids) { - counts[id] = await Member.count({ where: { roles: { id }, guild_id } }); - } + const role_ids = await Role.find({ where: { guild_id }, select: ["id"] }); + const counts: { [id: string]: number } = {}; + for (const { id } of role_ids) { + counts[id] = await Member.count({ where: { roles: { id }, guild_id } }); + } - return res.json(counts); + return res.json(counts); }); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/stickers.ts" "b/src/api/routes/guilds/\043guild_id/stickers.ts" index 231c3b5..f3a9f52 100644 --- "a/src/api/routes/guilds/\043guild_id/stickers.ts" +++ "b/src/api/routes/guilds/\043guild_id/stickers.ts" @@ -25,185 +25,185 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIStickerArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + "/", + route({ + responses: { + 200: { + body: "APIStickerArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json(await Sticker.find({ where: { guild_id } })); - }, + res.json(await Sticker.find({ where: { guild_id } })); + }, ); const bodyParser = multer({ - limits: { - fileSize: 1024 * 1024 * 100, - fields: 10, - files: 1, - }, - storage: multer.memoryStorage(), + limits: { + fileSize: 1024 * 1024 * 100, + fields: 10, + files: 1, + }, + storage: multer.memoryStorage(), }).single("file"); router.post( - "/", - bodyParser, - route({ - permission: "MANAGE_EMOJIS_AND_STICKERS", - requestBody: "ModifyGuildStickerSchema", - responses: { - 200: { - body: "Sticker", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - if (!req.file) throw new HTTPError("missing file"); + "/", + bodyParser, + route({ + permission: "MANAGE_EMOJIS_AND_STICKERS", + requestBody: "ModifyGuildStickerSchema", + responses: { + 200: { + body: "Sticker", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + if (!req.file) throw new HTTPError("missing file"); - const { guild_id } = req.params; - const body = req.body as ModifyGuildStickerSchema; - const id = Snowflake.generate(); + const { guild_id } = req.params; + const body = req.body as ModifyGuildStickerSchema; + const id = Snowflake.generate(); - const sticker_count = await Sticker.count({ - where: { guild_id: guild_id }, - }); - const { maxStickers } = Config.get().limits.guild; + const sticker_count = await Sticker.count({ + where: { guild_id: guild_id }, + }); + const { maxStickers } = Config.get().limits.guild; - if (sticker_count >= maxStickers) throw DiscordApiErrors.MAXIMUM_STICKERS.withParams(maxStickers); + if (sticker_count >= maxStickers) throw DiscordApiErrors.MAXIMUM_STICKERS.withParams(maxStickers); - const [sticker] = await Promise.all([ - Sticker.create({ - ...body, - guild_id, - id, - type: StickerType.GUILD, - format_type: getStickerFormat(req.file.mimetype), - available: true, - }).save(), - uploadFile(`/stickers/${id}`, req.file), - ]); + const [sticker] = await Promise.all([ + Sticker.create({ + ...body, + guild_id, + id, + type: StickerType.GUILD, + format_type: getStickerFormat(req.file.mimetype), + available: true, + }).save(), + uploadFile(`/stickers/${id}`, req.file), + ]); - await sendStickerUpdateEvent(guild_id); + await sendStickerUpdateEvent(guild_id); - res.json(sticker); - }, + res.json(sticker); + }, ); function getStickerFormat(mime_type: string) { - switch (mime_type) { - case "image/apng": - return StickerFormatType.APNG; - case "application/json": - return StickerFormatType.LOTTIE; - case "image/png": - return StickerFormatType.PNG; - case "image/gif": - return StickerFormatType.GIF; - default: - throw new HTTPError("invalid sticker format: must be png, apng or lottie"); - } + switch (mime_type) { + case "image/apng": + return StickerFormatType.APNG; + case "application/json": + return StickerFormatType.LOTTIE; + case "image/png": + return StickerFormatType.PNG; + case "image/gif": + return StickerFormatType.GIF; + default: + throw new HTTPError("invalid sticker format: must be png, apng or lottie"); + } } router.get( - "/:sticker_id", - route({ - responses: { - 200: { - body: "Sticker", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, sticker_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + "/:sticker_id", + route({ + responses: { + 200: { + body: "Sticker", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, sticker_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json( - await Sticker.findOneOrFail({ - where: { guild_id, id: sticker_id }, - }), - ); - }, + res.json( + await Sticker.findOneOrFail({ + where: { guild_id, id: sticker_id }, + }), + ); + }, ); router.patch( - "/:sticker_id", - route({ - requestBody: "ModifyGuildStickerSchema", - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 200: { - body: "Sticker", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, sticker_id } = req.params; - const body = req.body as ModifyGuildStickerSchema; + "/:sticker_id", + route({ + requestBody: "ModifyGuildStickerSchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 200: { + body: "Sticker", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, sticker_id } = req.params; + const body = req.body as ModifyGuildStickerSchema; - const sticker = await Sticker.create({ - ...body, - guild_id, - id: sticker_id, - }).save(); - await sendStickerUpdateEvent(guild_id); + const sticker = await Sticker.create({ + ...body, + guild_id, + id: sticker_id, + }).save(); + await sendStickerUpdateEvent(guild_id); - return res.json(sticker); - }, + return res.json(sticker); + }, ); async function sendStickerUpdateEvent(guild_id: string) { - return emitEvent({ - event: "GUILD_STICKERS_UPDATE", - guild_id: guild_id, - data: { - guild_id: guild_id, - stickers: await Sticker.find({ where: { guild_id: guild_id } }), - }, - } as GuildStickersUpdateEvent); + return emitEvent({ + event: "GUILD_STICKERS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + stickers: await Sticker.find({ where: { guild_id: guild_id } }), + }, + } as GuildStickersUpdateEvent); } router.delete( - "/:sticker_id", - route({ - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, sticker_id } = req.params; + "/:sticker_id", + route({ + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, sticker_id } = req.params; - await Sticker.delete({ guild_id, id: sticker_id }); - await sendStickerUpdateEvent(guild_id); + await Sticker.delete({ guild_id, id: sticker_id }); + await sendStickerUpdateEvent(guild_id); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/templates.ts" "b/src/api/routes/guilds/\043guild_id/templates.ts" index ef9d12c..2cc9604 100644 --- "a/src/api/routes/guilds/\043guild_id/templates.ts" +++ "b/src/api/routes/guilds/\043guild_id/templates.ts" @@ -24,160 +24,160 @@ const router: Router = Router({ mergeParams: true }); const TemplateGuildProjection: (keyof Guild)[] = [ - "id", - "name", - "description", - "region", - "verification_level", - "default_message_notifications", - "explicit_content_filter", - "preferred_locale", - "afk_timeout", - // "roles", - // "channels", - "afk_channel_id", - "system_channel_id", - "system_channel_flags", - "icon", + "id", + "name", + "description", + "region", + "verification_level", + "default_message_notifications", + "explicit_content_filter", + "preferred_locale", + "afk_timeout", + // "roles", + // "channels", + "afk_channel_id", + "system_channel_id", + "system_channel_flags", + "icon", ]; router.get( - "/", - route({ - responses: { - 200: { - body: "APITemplateArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "APITemplateArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const templates = await Template.find({ - where: { source_guild_id: guild_id }, - }); + const templates = await Template.find({ + where: { source_guild_id: guild_id }, + }); - return res.json(templates); - }, + return res.json(templates); + }, ); router.post( - "/", - route({ - requestBody: "TemplateCreateSchema", - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "Template", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: TemplateGuildProjection, - relations: ["roles", "channels"], - }); - const exists = await Template.findOne({ - where: { id: guild_id }, - }); - if (exists) throw new HTTPError("Template already exists", 400); + "/", + route({ + requestBody: "TemplateCreateSchema", + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "Template", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: TemplateGuildProjection, + relations: ["roles", "channels"], + }); + const exists = await Template.findOne({ + where: { id: guild_id }, + }); + if (exists) throw new HTTPError("Template already exists", 400); - const template = await Template.create({ - ...req.body, - code: generateCode(), - creator_id: req.user_id, - created_at: new Date(), - updated_at: new Date(), - source_guild_id: guild_id, - serialized_source_guild: guild, - }).save(); + const template = await Template.create({ + ...req.body, + code: generateCode(), + creator_id: req.user_id, + created_at: new Date(), + updated_at: new Date(), + source_guild_id: guild_id, + serialized_source_guild: guild, + }).save(); - res.json(template); - }, + res.json(template); + }, ); router.delete( - "/:code", - route({ - permission: "MANAGE_GUILD", - responses: { - 200: { body: "Template" }, - 403: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { code, guild_id } = req.params; + "/:code", + route({ + permission: "MANAGE_GUILD", + responses: { + 200: { body: "Template" }, + 403: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; - const template = await Template.delete({ - code, - source_guild_id: guild_id, - }); + const template = await Template.delete({ + code, + source_guild_id: guild_id, + }); - res.json(template); - }, + res.json(template); + }, ); router.put( - "/:code", - route({ - permission: "MANAGE_GUILD", - responses: { - 200: { body: "Template" }, - 403: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { code, guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: TemplateGuildProjection, - }); + "/:code", + route({ + permission: "MANAGE_GUILD", + responses: { + 200: { body: "Template" }, + 403: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: TemplateGuildProjection, + }); - const template = await Template.create({ - code, - serialized_source_guild: guild, - }).save(); + const template = await Template.create({ + code, + serialized_source_guild: guild, + }).save(); - res.json(template); - }, + res.json(template); + }, ); router.patch( - "/:code", - route({ - requestBody: "TemplateModifySchema", - permission: "MANAGE_GUILD", - responses: { - 200: { body: "Template" }, - 403: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { code, guild_id } = req.params; - const { name, description } = req.body; + "/:code", + route({ + requestBody: "TemplateModifySchema", + permission: "MANAGE_GUILD", + responses: { + 200: { body: "Template" }, + 403: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; + const { name, description } = req.body; - const template = await Template.findOneOrFail({ - where: { code, source_guild_id: guild_id }, - }); + const template = await Template.findOneOrFail({ + where: { code, source_guild_id: guild_id }, + }); - template.name = name; - template.description = description; + template.name = name; + template.description = description; - await template.save(); + await template.save(); - res.json(template); - }, + res.json(template); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/vanity-url.ts" "b/src/api/routes/guilds/\043guild_id/vanity-url.ts" index 77d72ed..e622c25 100644 --- "a/src/api/routes/guilds/\043guild_id/vanity-url.ts" +++ "b/src/api/routes/guilds/\043guild_id/vanity-url.ts" @@ -27,96 +27,96 @@ const InviteRegex = /\W/g; router.get( - "/", - route({ - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "GuildVanityUrlResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + "/", + route({ + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "GuildVanityUrlResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.features.includes("ALIASABLE_NAMES")) { - const invite = await Invite.findOne({ - where: { guild_id: guild_id, vanity_url: true }, - }); - if (!invite) return res.json({ code: null }); + if (!guild.features.includes("ALIASABLE_NAMES")) { + const invite = await Invite.findOne({ + where: { guild_id: guild_id, vanity_url: true }, + }); + if (!invite) return res.json({ code: null }); - return res.json({ code: invite.code, uses: invite.uses }); - } else { - const invite = await Invite.find({ - where: { guild_id: guild_id, vanity_url: true }, - }); - if (!invite || invite.length == 0) return res.json({ code: null }); + return res.json({ code: invite.code, uses: invite.uses }); + } else { + const invite = await Invite.find({ + where: { guild_id: guild_id, vanity_url: true }, + }); + if (!invite || invite.length == 0) return res.json({ code: null }); - return res.json(invite.map((x) => ({ code: x.code, uses: x.uses }))); - } - }, + return res.json(invite.map((x) => ({ code: x.code, uses: x.uses }))); + } + }, ); router.patch( - "/", - route({ - requestBody: "VanityUrlSchema", - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "GuildVanityUrlCreateResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as VanityUrlSchema; - const code = body.code?.replace(InviteRegex, ""); + "/", + route({ + requestBody: "VanityUrlSchema", + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "GuildVanityUrlCreateResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as VanityUrlSchema; + const code = body.code?.replace(InviteRegex, ""); - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls"); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls"); - if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty"); + if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty"); - const invite = await Invite.findOne({ where: { code } }); - if (invite) throw new HTTPError("Invite already exists"); + const invite = await Invite.findOne({ where: { code } }); + if (invite) throw new HTTPError("Invite already exists"); - const { id } = await Channel.findOneOrFail({ - where: { guild_id, type: ChannelType.GUILD_TEXT }, - }); + const { id } = await Channel.findOneOrFail({ + where: { guild_id, type: ChannelType.GUILD_TEXT }, + }); - if (!guild.features.includes("ALIASABLE_NAMES")) { - await Invite.delete({ guild_id, vanity_url: true }); - } + if (!guild.features.includes("ALIASABLE_NAMES")) { + await Invite.delete({ guild_id, vanity_url: true }); + } - await Invite.create({ - vanity_url: true, - code, - temporary: false, - uses: 0, - max_uses: 0, - max_age: 0, - created_at: new Date(), - guild_id: guild_id, - channel_id: id, - flags: 0, - }).save(); + await Invite.create({ + vanity_url: true, + code, + temporary: false, + uses: 0, + max_uses: 0, + max_age: 0, + created_at: new Date(), + guild_id: guild_id, + channel_id: id, + flags: 0, + }).save(); - return res.json({ code }); - }, + return res.json({ code }); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/voice-states/\043user_id/index.ts" "b/src/api/routes/guilds/\043guild_id/voice-states/\043user_id/index.ts" index 38be139..5283ae0 100644 --- "a/src/api/routes/guilds/\043guild_id/voice-states/\043user_id/index.ts" +++ "b/src/api/routes/guilds/\043guild_id/voice-states/\043user_id/index.ts" @@ -25,67 +25,67 @@ //TODO need more testing when community guild and voice stage channel are working router.patch( - "/", - route({ - requestBody: "VoiceStateUpdateSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as VoiceStateUpdateSchema; - const { guild_id } = req.params; - const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id; + "/", + route({ + requestBody: "VoiceStateUpdateSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as VoiceStateUpdateSchema; + const { guild_id } = req.params; + const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id; - const perms = await getPermission(req.user_id, guild_id, body.channel_id); + const perms = await getPermission(req.user_id, guild_id, body.channel_id); - /* + /* From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself. You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak. */ - if (body.suppress && user_id !== req.user_id) { - perms.hasThrow("MUTE_MEMBERS"); - } - if (!body.suppress) body.request_to_speak_timestamp = new Date(); - if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); + if (body.suppress && user_id !== req.user_id) { + perms.hasThrow("MUTE_MEMBERS"); + } + if (!body.suppress) body.request_to_speak_timestamp = new Date(); + if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); - const voice_state = await VoiceState.findOne({ - where: { - guild_id, - channel_id: body.channel_id, - user_id, - }, - }); - if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; + const voice_state = await VoiceState.findOne({ + where: { + guild_id, + channel_id: body.channel_id, + user_id, + }, + }); + if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; - voice_state.assign(body); - const channel = await Channel.findOneOrFail({ - where: { guild_id, id: body.channel_id }, - }); - if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { - throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; - } + voice_state.assign(body); + const channel = await Channel.findOneOrFail({ + where: { guild_id, id: body.channel_id }, + }); + if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { + throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; + } - await Promise.all([ - voice_state.save(), - emitEvent({ - event: "VOICE_STATE_UPDATE", - data: voice_state.toPublicVoiceState(), - guild_id, - } as VoiceStateUpdateEvent), - ]); - return res.sendStatus(204); - }, + await Promise.all([ + voice_state.save(), + emitEvent({ + event: "VOICE_STATE_UPDATE", + data: voice_state.toPublicVoiceState(), + guild_id, + } as VoiceStateUpdateEvent), + ]); + return res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/webhooks.ts" "b/src/api/routes/guilds/\043guild_id/webhooks.ts" index 49f34b6..81837d3 100644 --- "a/src/api/routes/guilds/\043guild_id/webhooks.ts" +++ "b/src/api/routes/guilds/\043guild_id/webhooks.ts" @@ -22,31 +22,31 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - description: "Returns a list of guild webhook objects. Requires the MANAGE_WEBHOOKS permission.", - permission: "MANAGE_WEBHOOKS", - responses: { - 200: { - body: "APIWebhookArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const webhooks = await Webhook.find({ - where: { guild_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + "/", + route({ + description: "Returns a list of guild webhook objects. Requires the MANAGE_WEBHOOKS permission.", + permission: "MANAGE_WEBHOOKS", + responses: { + 200: { + body: "APIWebhookArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const webhooks = await Webhook.find({ + where: { guild_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - const instanceUrl = Config.get().api.endpointPublic; - return res.json( - webhooks.map((webhook) => ({ - ...webhook, - url: instanceUrl + "/webhooks/" + webhook.id + "/" + webhook.token, - })), - ); - }, + const instanceUrl = Config.get().api.endpointPublic; + return res.json( + webhooks.map((webhook) => ({ + ...webhook, + url: instanceUrl + "/webhooks/" + webhook.id + "/" + webhook.token, + })), + ); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/welcome-screen.ts" "b/src/api/routes/guilds/\043guild_id/welcome-screen.ts" index a8956ee..474d597 100644 --- "a/src/api/routes/guilds/\043guild_id/welcome-screen.ts" +++ "b/src/api/routes/guilds/\043guild_id/welcome-screen.ts" @@ -24,69 +24,69 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildWelcomeScreen", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; + "/", + route({ + responses: { + 200: { + body: "GuildWelcomeScreen", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const guild_id = req.params.guild_id; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - await Member.IsInGuildOrFail(req.user_id, guild_id); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json(guild.welcome_screen); - }, + res.json(guild.welcome_screen); + }, ); router.patch( - "/", - route({ - requestBody: "GuildUpdateWelcomeScreenSchema", - permission: "MANAGE_GUILD", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const body = req.body as GuildUpdateWelcomeScreenSchema; + "/", + route({ + requestBody: "GuildUpdateWelcomeScreenSchema", + permission: "MANAGE_GUILD", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const guild_id = req.params.guild_id; + const body = req.body as GuildUpdateWelcomeScreenSchema; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled; + if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled; - if (body.description != undefined) guild.welcome_screen.description = body.description; + if (body.description != undefined) guild.welcome_screen.description = body.description; - if (body.welcome_channels != undefined) { - // Ensure channels exist within the guild - await Promise.all( - body.welcome_channels?.map(({ channel_id }) => - Channel.findOneOrFail({ - where: { id: channel_id, guild_id }, - select: { id: true }, - }), - ) || [], - ); - guild.welcome_screen.welcome_channels = body.welcome_channels; - } + if (body.welcome_channels != undefined) { + // Ensure channels exist within the guild + await Promise.all( + body.welcome_channels?.map(({ channel_id }) => + Channel.findOneOrFail({ + where: { id: channel_id, guild_id }, + select: { id: true }, + }), + ) || [], + ); + guild.welcome_screen.welcome_channels = body.welcome_channels; + } - await guild.save(); + await guild.save(); - res.status(200).json(guild.welcome_screen); - }, + res.status(200).json(guild.welcome_screen); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/widget.json.ts" "b/src/api/routes/guilds/\043guild_id/widget.json.ts" index d6f958f..8f41417 100644 --- "a/src/api/routes/guilds/\043guild_id/widget.json.ts" +++ "b/src/api/routes/guilds/\043guild_id/widget.json.ts" @@ -32,88 +32,88 @@ // https://discord.com/developers/docs/resources/guild#get-guild-widget // TODO: Cache the response for a guild for 5 minutes regardless of response router.get( - "/", - route({ - responses: { - 200: { - body: "GuildWidgetJsonResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "GuildWidgetJsonResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { - channel_ordering: true, - widget_channel_id: true, - widget_enabled: true, - presence_count: true, - name: true, - }, - }); - if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { + channel_ordering: true, + widget_channel_id: true, + widget_enabled: true, + presence_count: true, + name: true, + }, + }); + if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; - // Fetch existing widget invite for widget channel - let invite = await Invite.findOne({ - where: { channel_id: guild.widget_channel_id }, - }); + // Fetch existing widget invite for widget channel + let invite = await Invite.findOne({ + where: { channel_id: guild.widget_channel_id }, + }); - if (guild.widget_channel_id && !invite) { - // Create invite for channel if none exists - // TODO: Refactor invite create code to a shared function - const max_age = 86400; // 24 hours - const expires_at = new Date(max_age * 1000 + Date.now()); + if (guild.widget_channel_id && !invite) { + // Create invite for channel if none exists + // TODO: Refactor invite create code to a shared function + const max_age = 86400; // 24 hours + const expires_at = new Date(max_age * 1000 + Date.now()); - invite = await Invite.create({ - code: randomString(), - temporary: false, - uses: 0, - max_uses: 0, - max_age: max_age, - expires_at, - created_at: new Date(), - guild_id, - channel_id: guild.widget_channel_id, - flags: 0, - }).save(); - } + invite = await Invite.create({ + code: randomString(), + temporary: false, + uses: 0, + max_uses: 0, + max_age: max_age, + expires_at, + created_at: new Date(), + guild_id, + channel_id: guild.widget_channel_id, + flags: 0, + }).save(); + } - // Fetch voice channels, and the @everyone permissions object - const channels: { id: string; name: string; position: number }[] = []; + // Fetch voice channels, and the @everyone permissions object + const channels: { id: string; name: string; position: number }[] = []; - (await Channel.getOrderedChannels(guild.id, guild)).filter((doc) => { - // Only return channels where @everyone has the CONNECT permission - if (doc.permission_overwrites === undefined || Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT) { - channels.push({ - id: doc.id, - name: doc.name ?? "Unknown channel", - position: doc.position ?? 0, - }); - } - }); + (await Channel.getOrderedChannels(guild.id, guild)).filter((doc) => { + // Only return channels where @everyone has the CONNECT permission + if (doc.permission_overwrites === undefined || Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT) { + channels.push({ + id: doc.id, + name: doc.name ?? "Unknown channel", + position: doc.position ?? 0, + }); + } + }); - // Fetch members - // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) - const members = await Member.find({ where: { guild_id: guild_id } }); + // Fetch members + // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) + const members = await Member.find({ where: { guild_id: guild_id } }); - // Construct object to respond with - const data = { - id: guild_id, - name: guild.name, - instant_invite: invite?.code, - channels: channels, - members: members, - presence_count: guild.presence_count, - }; + // Construct object to respond with + const data = { + id: guild_id, + name: guild.name, + instant_invite: invite?.code, + channels: channels, + members: members, + presence_count: guild.presence_count, + }; - res.set("Cache-Control", "public, max-age=300"); - return res.json(data); - }, + res.set("Cache-Control", "public, max-age=300"); + return res.json(data); + }, ); export default router; diff --git "a/src/api/routes/guilds/\043guild_id/widget.png.ts" "b/src/api/routes/guilds/\043guild_id/widget.png.ts" index a7a9b60..b0abc95 100644 --- "a/src/api/routes/guilds/\043guild_id/widget.png.ts" +++ "b/src/api/routes/guilds/\043guild_id/widget.png.ts" @@ -33,112 +33,112 @@ // https://discord.com/developers/docs/resources/guild#get-guild-widget-image // TODO: Cache the response router.get( - "/", - route({ - responses: { - 200: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; - // Fetch guild information - const icon = "avatars/" + guild_id + "/" + guild.icon; - const name = guild.name; - const presence = guild.presence_count + " ONLINE"; + // Fetch guild information + const icon = "avatars/" + guild_id + "/" + guild.icon; + const name = guild.name; + const presence = guild.presence_count + " ONLINE"; - // Fetch parameter - const style = req.query.style?.toString() || "shield"; - if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) { - throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); - } + // Fetch parameter + const style = req.query.style?.toString() || "shield"; + if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) { + throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); + } - // Setup canvas - const { createCanvas, loadImage } = require("canvas"); - const sizeOf = require("image-size"); + // Setup canvas + const { createCanvas, loadImage } = require("canvas"); + const sizeOf = require("image-size"); - // TODO: Widget style templates need Spacebar branding - const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`); - if (!fs.existsSync(source)) { - throw new HTTPError("Widget template does not exist.", 400); - } + // TODO: Widget style templates need Spacebar branding + const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`); + if (!fs.existsSync(source)) { + throw new HTTPError("Widget template does not exist.", 400); + } - // Create base template image for parameter - const { width, height } = await sizeOf(source); - const canvas = createCanvas(width, height); - const ctx = canvas.getContext("2d"); - const template = await loadImage(source); - ctx.drawImage(template, 0, 0); + // Create base template image for parameter + const { width, height } = await sizeOf(source); + const canvas = createCanvas(width, height); + const ctx = canvas.getContext("2d"); + const template = await loadImage(source); + ctx.drawImage(template, 0, 0); - // Add the guild specific information to the template asset image - switch (style) { - case "shield": - ctx.textAlign = "center"; - await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence); - break; - case "banner1": - if (icon) await drawIcon(ctx, 20, 27, 50, icon); - await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22); - await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence); - break; - case "banner2": - if (icon) await drawIcon(ctx, 13, 19, 36, icon); - await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15); - await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence); - break; - case "banner3": - if (icon) await drawIcon(ctx, 20, 20, 50, icon); - await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27); - await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence); - break; - case "banner4": - if (icon) await drawIcon(ctx, 21, 136, 50, icon); - await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27); - await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence); - break; - default: - throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); - } + // Add the guild specific information to the template asset image + switch (style) { + case "shield": + ctx.textAlign = "center"; + await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence); + break; + case "banner1": + if (icon) await drawIcon(ctx, 20, 27, 50, icon); + await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22); + await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence); + break; + case "banner2": + if (icon) await drawIcon(ctx, 13, 19, 36, icon); + await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15); + await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence); + break; + case "banner3": + if (icon) await drawIcon(ctx, 20, 20, 50, icon); + await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27); + await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence); + break; + case "banner4": + if (icon) await drawIcon(ctx, 21, 136, 50, icon); + await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27); + await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence); + break; + default: + throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); + } - // Return final image - const buffer = canvas.toBuffer("image/png"); - res.set("Content-Type", "image/png"); - res.set("Cache-Control", "public, max-age=3600"); - return res.send(buffer); - }, + // Return final image + const buffer = canvas.toBuffer("image/png"); + res.set("Content-Type", "image/png"); + res.set("Cache-Control", "public, max-age=3600"); + return res.send(buffer); + }, ); async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) { - const { loadImage } = require("canvas"); - const img = await loadImage(await storage.get(icon)); + const { loadImage } = require("canvas"); + const img = await loadImage(await storage.get(icon)); - // Do some canvas clipping magic! - canvas.save(); - canvas.beginPath(); + // Do some canvas clipping magic! + canvas.save(); + canvas.beginPath(); - const r = scale / 2; // use scale to determine radius - canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center + const r = scale / 2; // use scale to determine radius + canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center - canvas.clip(); - canvas.drawImage(img, x, y, scale, scale); + canvas.clip(); + canvas.drawImage(img, x, y, scale, scale); - canvas.restore(); + canvas.restore(); } async function drawText(canvas: any, x: number, y: number, color: string, font: string, text: string, maxcharacters?: number) { - canvas.fillStyle = color; - canvas.font = font; - if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "..."; - canvas.fillText(text, x, y); + canvas.fillStyle = color; + canvas.font = font; + if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "..."; + canvas.fillText(text, x, y); } export default router; diff --git "a/src/api/routes/guilds/\043guild_id/widget.ts" "b/src/api/routes/guilds/\043guild_id/widget.ts" index cad26f3..5a2fdb9 100644 --- "a/src/api/routes/guilds/\043guild_id/widget.ts" +++ "b/src/api/routes/guilds/\043guild_id/widget.ts" @@ -25,62 +25,62 @@ // https://discord.com/developers/docs/resources/guild#get-guild-widget-settings router.get( - "/", - route({ - responses: { - 200: { - body: "GuildWidgetSettingsResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "GuildWidgetSettingsResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - return res.json({ - enabled: guild.widget_enabled || false, - channel_id: guild.widget_channel_id || null, - }); - }, + return res.json({ + enabled: guild.widget_enabled || false, + channel_id: guild.widget_channel_id || null, + }); + }, ); // https://discord.com/developers/docs/resources/guild#modify-guild-widget router.patch( - "/", - route({ - requestBody: "WidgetModifySchema", - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "WidgetModifySchema", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as WidgetModifySchema; - const { guild_id } = req.params; + "/", + route({ + requestBody: "WidgetModifySchema", + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "WidgetModifySchema", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as WidgetModifySchema; + const { guild_id } = req.params; - await Guild.update( - { id: guild_id }, - { - widget_enabled: body.enabled, - widget_channel_id: body.channel_id, - }, - ); - // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request + await Guild.update( + { id: guild_id }, + { + widget_enabled: body.enabled, + widget_channel_id: body.channel_id, + }, + ); + // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request - return res.json(body); - }, + return res.json(body); + }, ); export default router; diff --git a/src/api/routes/guilds/automations/email-domain-lookup.ts b/src/api/routes/guilds/automations/email-domain-lookup.ts index 93ae231..605026b 100644 --- a/src/api/routes/guilds/automations/email-domain-lookup.ts +++ b/src/api/routes/guilds/automations/email-domain-lookup.ts @@ -26,74 +26,74 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "EmailDomainLookupSchema", - responses: { - 200: { - body: "EmailDomainLookupResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { email } = req.body as EmailDomainLookupSchema; + "/", + route({ + requestBody: "EmailDomainLookupSchema", + responses: { + 200: { + body: "EmailDomainLookupResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { email } = req.body as EmailDomainLookupSchema; - const [_, tld] = email.split("@"); + const [_, tld] = email.split("@"); - if (emailProviders.includes(tld.toLowerCase())) { - throw FieldErrors({ - name: { - message: "That looks like a personal email address. Please use your official student email.", - code: "EMAIL_IS_UNOFFICIAL", - }, - }); - } + if (emailProviders.includes(tld.toLowerCase())) { + throw FieldErrors({ + name: { + message: "That looks like a personal email address. Please use your official student email.", + code: "EMAIL_IS_UNOFFICIAL", + }, + }); + } - return res.json({ - guilds_info: [], - has_matching_guild: false, - } as EmailDomainLookupResponse); - }, + return res.json({ + guilds_info: [], + has_matching_guild: false, + } as EmailDomainLookupResponse); + }, ); router.post( - "/verify-code", - route({ - requestBody: "EmailDomainLookupVerifyCodeSchema", - responses: { - // 200: { - // body: "EmailDomainLookupVerifyCodeResponse", - // }, - 400: { - body: "APIErrorResponse", - }, - 501: {}, - }, - }), - async (req: Request, res: Response) => { - const { email } = req.body as EmailDomainLookupVerifyCodeSchema; + "/verify-code", + route({ + requestBody: "EmailDomainLookupVerifyCodeSchema", + responses: { + // 200: { + // body: "EmailDomainLookupVerifyCodeResponse", + // }, + 400: { + body: "APIErrorResponse", + }, + 501: {}, + }, + }), + async (req: Request, res: Response) => { + const { email } = req.body as EmailDomainLookupVerifyCodeSchema; - const [_, tld] = email.split("@"); + const [_, tld] = email.split("@"); - if (emailProviders.includes(tld.toLowerCase())) { - throw FieldErrors({ - name: { - message: "That looks like a personal email address. Please use your official student email.", - code: "EMAIL_IS_UNOFFICIAL", - }, - }); - } + if (emailProviders.includes(tld.toLowerCase())) { + throw FieldErrors({ + name: { + message: "That looks like a personal email address. Please use your official student email.", + code: "EMAIL_IS_UNOFFICIAL", + }, + }); + } - throw new HTTPError("Not implemented", 501); + throw new HTTPError("Not implemented", 501); - // return res.json({ - // guild: null, - // joined: false, - // } as EmailDomainLookupVerifyCodeResponse); - }, + // return res.json({ + // guild: null, + // joined: false, + // } as EmailDomainLookupVerifyCodeResponse); + }, ); export default router; diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts index 6f60fa6..78ba93a 100644 --- a/src/api/routes/guilds/index.ts +++ b/src/api/routes/guilds/index.ts @@ -26,49 +26,49 @@ //TODO: create default channel router.post( - "/", - route({ - requestBody: "GuildCreateSchema", - right: "CREATE_GUILDS", - responses: { - 201: { - body: "GuildCreateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as GuildCreateSchema; + "/", + route({ + requestBody: "GuildCreateSchema", + right: "CREATE_GUILDS", + responses: { + 201: { + body: "GuildCreateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as GuildCreateSchema; - const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ where: { id: req.user_id } }); - const rights = await getRights(req.user_id); - if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) { - throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); - } + const { maxGuilds } = Config.get().limits.user; + const guild_count = await Member.count({ where: { id: req.user_id } }); + const rights = await getRights(req.user_id); + if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) { + throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); + } - const guild = await Guild.createGuild({ - ...body, - owner_id: req.user_id, - template_guild_id: null, - }); + const guild = await Guild.createGuild({ + ...body, + owner_id: req.user_id, + template_guild_id: null, + }); - const { autoJoin } = Config.get().guild; - if (autoJoin.enabled && !autoJoin.guilds?.length) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); - } + const { autoJoin } = Config.get().guild; + if (autoJoin.enabled && !autoJoin.guilds?.length) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); + } - await Member.addToGuild(req.user_id, guild.id); + await Member.addToGuild(req.user_id, guild.id); - res.status(201).json(guild); - }, + res.status(201).json(guild); + }, ); export default router; diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts index 50ba017..e5dfd68 100644 --- a/src/api/routes/guilds/templates/index.ts +++ b/src/api/routes/guilds/templates/index.ts @@ -25,80 +25,80 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/:template_code", - route({ - responses: { - 200: { - body: "Template", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { template_code } = req.params; + "/:template_code", + route({ + responses: { + 200: { + body: "Template", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { template_code } = req.params; - const template = await getTemplate(template_code); + const template = await getTemplate(template_code); - res.json(template); - }, + res.json(template); + }, ); router.post("/:template_code", route({ requestBody: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => { - const { template_code } = req.params; - const body = req.body as GuildTemplateCreateSchema; + const { template_code } = req.params; + const body = req.body as GuildTemplateCreateSchema; - const { maxGuilds } = Config.get().limits.user; + const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ where: { id: req.user_id } }); - if (guild_count >= maxGuilds) throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); + const guild_count = await Member.count({ where: { id: req.user_id } }); + if (guild_count >= maxGuilds) throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); - const template = (await getTemplate(template_code)) as Template; + const template = (await getTemplate(template_code)) as Template; - const guild = await Guild.createGuild({ - ...template.serialized_source_guild, - // body comes after the template - ...body, - owner_id: req.user_id, - template_guild_id: template.source_guild_id, - }); + const guild = await Guild.createGuild({ + ...template.serialized_source_guild, + // body comes after the template + ...body, + owner_id: req.user_id, + template_guild_id: template.source_guild_id, + }); - await Member.addToGuild(req.user_id, guild.id); + await Member.addToGuild(req.user_id, guild.id); - res.status(201).json({ id: guild.id }); + res.status(201).json({ id: guild.id }); }); async function getTemplate(code: string) { - const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates; + const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates; - if (!enabled) throw new HTTPError("Template creation & usage is disabled on this instance.", 403); + if (!enabled) throw new HTTPError("Template creation & usage is disabled on this instance.", 403); - if (code.startsWith("discord:")) { - if (!allowDiscordTemplates) throw new HTTPError("Discord templates cannot be used on this instance.", 403); + if (code.startsWith("discord:")) { + if (!allowDiscordTemplates) throw new HTTPError("Discord templates cannot be used on this instance.", 403); - const discordTemplateID = code.split("discord:", 2)[1]; + const discordTemplateID = code.split("discord:", 2)[1]; - const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }); + const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }); - return await discordTemplateData.json(); - } + return await discordTemplateData.json(); + } - if (code.startsWith("external:")) { - if (!allowRaws) throw new HTTPError("Importing raws is disabled on this instance.", 403); + if (code.startsWith("external:")) { + if (!allowRaws) throw new HTTPError("Importing raws is disabled on this instance.", 403); - return code.split("external:", 2)[1]; - } + return code.split("external:", 2)[1]; + } - return await Template.findOneOrFail({ - where: { code: code }, - }); + return await Template.findOneOrFail({ + where: { code: code }, + }); } export default router; diff --git a/src/api/routes/hub-waitlist.ts b/src/api/routes/hub-waitlist.ts index bdf0931..43c2662 100644 --- a/src/api/routes/hub-waitlist.ts +++ b/src/api/routes/hub-waitlist.ts @@ -22,28 +22,28 @@ const router = Router({ mergeParams: true }); router.post( - "/signup", - route({ - requestBody: "HubWaitlistSignupSchema", - responses: { - 200: { - body: "HubWaitlistSignupResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { email, school } = req.body as HubWaitlistSignupSchema; + "/signup", + route({ + requestBody: "HubWaitlistSignupSchema", + responses: { + 200: { + body: "HubWaitlistSignupResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { email, school } = req.body as HubWaitlistSignupSchema; - res.json({ - email, - email_domain: email.split("@")[1], - school, - user_id: req.user_id, - } as HubWaitlistSignupResponse); - }, + res.json({ + email, + email_domain: email.split("@")[1], + school, + user_id: req.user_id, + } as HubWaitlistSignupResponse); + }, ); export default router; diff --git "a/src/api/routes/interactions/\043interaction_id/\043interaction_token/callback.ts" "b/src/api/routes/interactions/\043interaction_id/\043interaction_token/callback.ts" index 442ce0e..e94d455 100644 --- "a/src/api/routes/interactions/\043interaction_id/\043interaction_token/callback.ts" +++ "b/src/api/routes/interactions/\043interaction_id/\043interaction_token/callback.ts" @@ -26,79 +26,79 @@ const router = Router({ mergeParams: true }); router.post("/", route({}), async (req: Request, res: Response) => { - const body = req.body as InteractionCallbackSchema; + const body = req.body as InteractionCallbackSchema; - const errors: Record = {}; - const knownComponentIds: string[] = []; + const errors: Record = {}; + const knownComponentIds: string[] = []; - for (const row of body.data.components || []) { - if (!row.components) { - continue; - } + for (const row of body.data.components || []) { + if (!row.components) { + continue; + } - if (row.components.length < 1 || row.components.length > 5) { - errors[`data.components[${body.data.components!.indexOf(row)}].components`] = { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 5 in length.`, - }; - } + if (row.components.length < 1 || row.components.length > 5) { + errors[`data.components[${body.data.components!.indexOf(row)}].components`] = { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 5 in length.`, + }; + } - for (const component of row.components) { - if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) { - if (component.custom_id?.trim() === "") { - errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { - code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED", - message: "A custom id required", - }; - } + for (const component of row.components) { + if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) { + if (component.custom_id?.trim() === "") { + errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { + code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED", + message: "A custom id required", + }; + } - if (knownComponentIds.includes(component.custom_id!)) { - errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { - code: "COMPONENT_CUSTOM_ID_DUPLICATED", - message: "Component custom id cannot be duplicated", - }; - } else { - knownComponentIds.push(component.custom_id!); - } - } - } - } + if (knownComponentIds.includes(component.custom_id!)) { + errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { + code: "COMPONENT_CUSTOM_ID_DUPLICATED", + message: "Component custom id cannot be duplicated", + }; + } else { + knownComponentIds.push(component.custom_id!); + } + } + } + } - if (Object.keys(errors).length > 0) { - throw FieldErrors(errors); - } + if (Object.keys(errors).length > 0) { + throw FieldErrors(errors); + } - const interactionId = req.params.interaction_id; - const interaction = pendingInteractions.get(req.params.interaction_id); + const interactionId = req.params.interaction_id; + const interaction = pendingInteractions.get(req.params.interaction_id); - if (!interaction) { - return; - } + if (!interaction) { + return; + } - clearTimeout(interaction.timeout); + clearTimeout(interaction.timeout); - emitEvent({ - event: "INTERACTION_SUCCESS", - user_id: interaction?.userId, - data: { - id: interactionId, - nonce: interaction?.nonce, - }, - } as InteractionSuccessEvent); + emitEvent({ + event: "INTERACTION_SUCCESS", + user_id: interaction?.userId, + data: { + id: interactionId, + nonce: interaction?.nonce, + }, + } as InteractionSuccessEvent); - switch (body.type) { - case InteractionCallbackType.PONG: - // TODO - break; - case InteractionCallbackType.ACKNOWLEDGE: - // Deprected - break; - case InteractionCallbackType.CHANNEL_MESSAGE: - // TODO - break; - case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: { - const user = await User.findOneOrFail({ where: { id: interaction.userId } }); - /* + switch (body.type) { + case InteractionCallbackType.PONG: + // TODO + break; + case InteractionCallbackType.ACKNOWLEDGE: + // Deprected + break; + case InteractionCallbackType.CHANNEL_MESSAGE: + // TODO + break; + case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: { + const user = await User.findOneOrFail({ where: { id: interaction.userId } }); + /* const files = (req.files as Express.Multer.File[]) ?? []; //I don't think traditional attachments are allowed anyways const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = []; @@ -111,71 +111,71 @@ } } */ - await sendMessage({ - type: MessageType.APPLICATION_COMMAND, - timestamp: new Date(), - application_id: interaction.applicationId, - channel_id: interaction.channelId, - author_id: interaction.applicationId, - nonce: interaction.nonce, - content: body.data.content, - components: body.data.components || [], - tts: body.data.tts, - embeds: body.data.embeds || [], - attachments: body.data.attachments, - poll: body.data.poll, - flags: body.data.flags, - reactions: [], - // webhook_id: interaction.applicationId, // This one requires a webhook to be created first - interaction: { - id: interactionId, - name: interaction.commandName, - type: 2, - user, - }, - interaction_metadata: { - id: interactionId, - type: 2, - user_id: interaction.userId, - user, - authorizing_integration_owners: { - "1": interaction.userId, - }, - name: interaction.commandName, - command_type: interaction.commandType, - }, - }); + await sendMessage({ + type: MessageType.APPLICATION_COMMAND, + timestamp: new Date(), + application_id: interaction.applicationId, + channel_id: interaction.channelId, + author_id: interaction.applicationId, + nonce: interaction.nonce, + content: body.data.content, + components: body.data.components || [], + tts: body.data.tts, + embeds: body.data.embeds || [], + attachments: body.data.attachments, + poll: body.data.poll, + flags: body.data.flags, + reactions: [], + // webhook_id: interaction.applicationId, // This one requires a webhook to be created first + interaction: { + id: interactionId, + name: interaction.commandName, + type: 2, + user, + }, + interaction_metadata: { + id: interactionId, + type: 2, + user_id: interaction.userId, + user, + authorizing_integration_owners: { + "1": interaction.userId, + }, + name: interaction.commandName, + command_type: interaction.commandType, + }, + }); - break; - } - case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE: - // TODO - break; - case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE: - // TODO - break; - case InteractionCallbackType.UPDATE_MESSAGE: - // TODO - break; - case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: - // TODO - break; - case InteractionCallbackType.MODAL: - // TODO - break; - case InteractionCallbackType.PREMIUM_REQUIRED: - // Deprecated - break; - case InteractionCallbackType.IFRAME_MODAL: - // TODO - break; - case InteractionCallbackType.LAUNCH_ACTIVITY: - // TODO - break; - } + break; + } + case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE: + // TODO + break; + case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE: + // TODO + break; + case InteractionCallbackType.UPDATE_MESSAGE: + // TODO + break; + case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: + // TODO + break; + case InteractionCallbackType.MODAL: + // TODO + break; + case InteractionCallbackType.PREMIUM_REQUIRED: + // Deprecated + break; + case InteractionCallbackType.IFRAME_MODAL: + // TODO + break; + case InteractionCallbackType.LAUNCH_ACTIVITY: + // TODO + break; + } - pendingInteractions.delete(interactionId); - res.sendStatus(204); + pendingInteractions.delete(interactionId); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/interactions/index.ts b/src/api/routes/interactions/index.ts index 9f15439..eb516ce 100644 --- a/src/api/routes/interactions/index.ts +++ b/src/api/routes/interactions/index.ts @@ -27,104 +27,104 @@ const router = Router({ mergeParams: true }); router.post("/", route({}), async (req: Request, res: Response) => { - const body = req.body as InteractionSchema; + const body = req.body as InteractionSchema; - const interactionId = Snowflake.generate(); - const interactionToken = randomBytes(24).toString("base64url"); + const interactionId = Snowflake.generate(); + const interactionToken = randomBytes(24).toString("base64url"); - emitEvent({ - event: "INTERACTION_CREATE", - user_id: req.user_id, - data: { - id: interactionId, - nonce: body.nonce, - }, - } as InteractionCreateEvent); + emitEvent({ + event: "INTERACTION_CREATE", + user_id: req.user_id, + data: { + id: interactionId, + nonce: body.nonce, + }, + } as InteractionCreateEvent); - const user = await User.findOneOrFail({ where: { id: req.user_id } }); + const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const interactionData: Partial = { - id: interactionId, - application_id: body.application_id, - channel_id: body.channel_id, - type: body.type, - token: interactionToken, - version: 1, - entitlements: [], - authorizing_integration_owners: { "0": req.user_id }, - attachment_size_limit: Config.get().cdn.maxAttachmentSize, - }; + const interactionData: Partial = { + id: interactionId, + application_id: body.application_id, + channel_id: body.channel_id, + type: body.type, + token: interactionToken, + version: 1, + entitlements: [], + authorizing_integration_owners: { "0": req.user_id }, + attachment_size_limit: Config.get().cdn.maxAttachmentSize, + }; - if (body.type === InteractionType.ApplicationCommand || body.type === InteractionType.MessageComponent || body.type === InteractionType.ModalSubmit) { - interactionData.data = body.data; - } + if (body.type === InteractionType.ApplicationCommand || body.type === InteractionType.MessageComponent || body.type === InteractionType.ModalSubmit) { + interactionData.data = body.data; + } - if (body.type != InteractionType.Ping) { - interactionData.locale = user?.settings?.locale; - } + if (body.type != InteractionType.Ping) { + interactionData.locale = user?.settings?.locale; + } - if (body.guild_id) { - interactionData.context = 0; - interactionData.guild_id = body.guild_id; - interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString(); + if (body.guild_id) { + interactionData.context = 0; + interactionData.guild_id = body.guild_id; + interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString(); - const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } }); - const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] }); + const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } }); + const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] }); - interactionData.guild = { - id: guild.id, - features: guild.features, - locale: guild.preferred_locale!, - }; + interactionData.guild = { + id: guild.id, + features: guild.features, + locale: guild.preferred_locale!, + }; - interactionData.guild_locale = guild.preferred_locale; - interactionData.member = member.toPublicMember(); - } else { - interactionData.user = user.toPublicUser(); - interactionData.app_permissions = (await getPermission(body.application_id, "", body.channel_id)).bitfield.toString(); + interactionData.guild_locale = guild.preferred_locale; + interactionData.member = member.toPublicMember(); + } else { + interactionData.user = user.toPublicUser(); + interactionData.app_permissions = (await getPermission(body.application_id, "", body.channel_id)).bitfield.toString(); - if (body.channel_id === body.application_id) { - interactionData.context = 1; - } else { - interactionData.context = 2; - } - } + if (body.channel_id === body.application_id) { + interactionData.context = 1; + } else { + interactionData.context = 2; + } + } - if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) { - interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: ["author"] }); - } + if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) { + interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: ["author"] }); + } - emitEvent({ - event: "INTERACTION_CREATE", - user_id: body.application_id, - data: interactionData, - } as InteractionCreateEvent); + emitEvent({ + event: "INTERACTION_CREATE", + user_id: body.application_id, + data: interactionData, + } as InteractionCreateEvent); - const interactionTimeout = setTimeout(() => { - emitEvent({ - event: "INTERACTION_FAILURE", - user_id: req.user_id, - data: { - id: interactionId, - nonce: body.nonce, - reason_code: 2, // when types are done: InteractionFailureReason.TIMEOUT, - }, - } as InteractionFailureEvent); - }, 3000); + const interactionTimeout = setTimeout(() => { + emitEvent({ + event: "INTERACTION_FAILURE", + user_id: req.user_id, + data: { + id: interactionId, + nonce: body.nonce, + reason_code: 2, // when types are done: InteractionFailureReason.TIMEOUT, + }, + } as InteractionFailureEvent); + }, 3000); - pendingInteractions.set(interactionId, { - timeout: interactionTimeout, - nonce: body.nonce, - applicationId: body.application_id, - userId: req.user_id, - guildId: body.guild_id, - channelId: body.channel_id, - type: body.type, - commandType: body.data.type, - commandName: body.data.name, - }); + pendingInteractions.set(interactionId, { + timeout: interactionTimeout, + nonce: body.nonce, + applicationId: body.application_id, + userId: req.user_id, + guildId: body.guild_id, + channelId: body.channel_id, + type: body.type, + commandType: body.data.type, + commandName: body.data.name, + }); - res.sendStatus(204); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts index 87715f3..26b0ad9 100644 --- a/src/api/routes/invites/index.ts +++ b/src/api/routes/invites/index.ts @@ -25,134 +25,134 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/:invite_code", - route({ - responses: { - "200": { - body: "Invite", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { invite_code } = req.params; + "/:invite_code", + route({ + responses: { + "200": { + body: "Invite", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { invite_code } = req.params; - const invite = await Invite.findOneOrFail({ - where: { code: invite_code }, - relations: PublicInviteRelation, - }); + const invite = await Invite.findOneOrFail({ + where: { code: invite_code }, + relations: PublicInviteRelation, + }); - res.status(200).send(invite); - }, + res.status(200).send(invite); + }, ); router.post( - "/:invite_code", - route({ - right: "USE_MASS_INVITES", - responses: { - "200": { - body: "Invite", - }, - 401: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT; + "/:invite_code", + route({ + right: "USE_MASS_INVITES", + responses: { + "200": { + body: "Invite", + }, + 401: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT; - const { invite_code } = req.params; - const { guild_id } = await Invite.findOneOrFail({ - where: { code: invite_code }, - }); - const { features } = await Guild.findOneOrFail({ - where: { id: guild_id }, - }); - const { public_flags } = await User.findOneOrFail({ - where: { id: req.user_id }, - }); - const ban = await Ban.findOne({ - where: [ - { guild_id: guild_id, user_id: req.user_id }, - { guild_id: guild_id, ip: req.ip }, - ], - }); + const { invite_code } = req.params; + const { guild_id } = await Invite.findOneOrFail({ + where: { code: invite_code }, + }); + const { features } = await Guild.findOneOrFail({ + where: { id: guild_id }, + }); + const { public_flags } = await User.findOneOrFail({ + where: { id: req.user_id }, + }); + const ban = await Ban.findOne({ + where: [ + { guild_id: guild_id, user_id: req.user_id }, + { guild_id: guild_id, ip: req.ip }, + ], + }); - if (ban) { - console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is banned by ${ban.user_id === req.user_id ? "User ID" : "IP address"}.`); - throw DiscordApiErrors.USER_BANNED; - } + if (ban) { + console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is banned by ${ban.user_id === req.user_id ? "User ID" : "IP address"}.`); + throw DiscordApiErrors.USER_BANNED; + } - if ((BigInt(public_flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) { - console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is quarantined.`); - throw DiscordApiErrors.UNKNOWN_INVITE; - } + if ((BigInt(public_flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) { + console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is quarantined.`); + throw DiscordApiErrors.UNKNOWN_INVITE; + } - if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) { - console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is not staff.`); - throw new HTTPError("Only intended for the staff of this instance.", 401); - } + if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) { + console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is not staff.`); + throw new HTTPError("Only intended for the staff of this instance.", 401); + } - if (features.includes("INVITES_DISABLED")) { - console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but joins are closed.`); - throw new HTTPError("Sorry, this guild has joins closed.", 403); - } + if (features.includes("INVITES_DISABLED")) { + console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but joins are closed.`); + throw new HTTPError("Sorry, this guild has joins closed.", 403); + } - const invite = await Invite.joinGuild(req.user_id, invite_code); + const invite = await Invite.joinGuild(req.user_id, invite_code); - res.json(invite); - }, + res.json(invite); + }, ); // * cant use permission of route() function because path doesn't have guild_id/channel_id router.delete( - "/:invite_code", - route({ - responses: { - "200": { - body: "Invite", - }, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { invite_code } = req.params; - const invite = await Invite.findOneOrFail({ where: { code: invite_code } }); - const { guild_id, channel_id } = invite; + "/:invite_code", + route({ + responses: { + "200": { + body: "Invite", + }, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { invite_code } = req.params; + const invite = await Invite.findOneOrFail({ where: { code: invite_code } }); + const { guild_id, channel_id } = invite; - const permission = await getPermission(req.user_id, guild_id, channel_id); + const permission = await getPermission(req.user_id, guild_id, channel_id); - if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401); + if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401); - await Promise.all([ - Invite.delete({ code: invite_code }), - emitEvent({ - event: "INVITE_DELETE", - guild_id: guild_id, - data: { - channel_id: channel_id, - guild_id: guild_id, - code: invite_code, - }, - } as InviteDeleteEvent), - ]); + await Promise.all([ + Invite.delete({ code: invite_code }), + emitEvent({ + event: "INVITE_DELETE", + guild_id: guild_id, + data: { + channel_id: channel_id, + guild_id: guild_id, + code: invite_code, + }, + } as InviteDeleteEvent), + ]); - res.json({ invite: invite }); - }, + res.json({ invite: invite }); + }, ); export default router; diff --git a/src/api/routes/oauth2/applications/@me.ts b/src/api/routes/oauth2/applications/@me.ts index 6daad79..9cdd47c 100644 --- a/src/api/routes/oauth2/applications/@me.ts +++ b/src/api/routes/oauth2/applications/@me.ts @@ -24,30 +24,30 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Application", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.params.id }, // ...huh? there's no ID in the path... - relations: ["bot", "owner"], - select: { - owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), - }, - }); + "/", + route({ + responses: { + 200: { + body: "Application", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.params.id }, // ...huh? there's no ID in the path... + relations: ["bot", "owner"], + select: { + owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), + }, + }); - if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; + if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; - res.json({ - ...app, - owner: app.owner.toPublicUser(), - install_params: app.install_params !== null ? app.install_params : undefined, - }); - }, + res.json({ + ...app, + owner: app.owner.toPublicUser(), + install_params: app.install_params !== null ? app.install_params : undefined, + }); + }, ); export default router; diff --git a/src/api/routes/oauth2/authorize.ts b/src/api/routes/oauth2/authorize.ts index 4084a05..4dea75c 100644 --- a/src/api/routes/oauth2/authorize.ts +++ b/src/api/routes/oauth2/authorize.ts @@ -25,198 +25,198 @@ // TODO: scopes, other oauth types router.get( - "/", - route({ - query: { - client_id: { - type: "string", - }, - }, - responses: { - // TODO: I really didn't feel like typing all of it out - 200: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // const { client_id, scope, response_type, redirect_url } = req.query; - const { client_id } = req.query; - if (!client_id) { - throw FieldErrors({ - client_id: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + "/", + route({ + query: { + client_id: { + type: "string", + }, + }, + responses: { + // TODO: I really didn't feel like typing all of it out + 200: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // const { client_id, scope, response_type, redirect_url } = req.query; + const { client_id } = req.query; + if (!client_id) { + throw FieldErrors({ + client_id: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - const app = await Application.findOne({ - where: { - id: client_id as string, - }, - relations: ["bot"], - }); + const app = await Application.findOne({ + where: { + id: client_id as string, + }, + relations: ["bot"], + }); - // TODO: use DiscordApiErrors - // findOneOrFail throws code 404 - if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION; - if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT; + // TODO: use DiscordApiErrors + // findOneOrFail throws code 404 + if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION; + if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT; - const bot = app.bot; - delete app.bot; + const bot = app.bot; + delete app.bot; - const user = await User.findOneOrFail({ - where: { - id: req.user_id, - bot: false, - }, - select: ["id", "username", "avatar", "discriminator", "public_flags"], - }); + const user = await User.findOneOrFail({ + where: { + id: req.user_id, + bot: false, + }, + select: ["id", "username", "avatar", "discriminator", "public_flags"], + }); - const guilds = await Member.find({ - where: { - id: req.user_id, - }, - relations: ["guild", "roles", "user"], - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - // prettier-ignore - select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"], - }); + const guilds = await Member.find({ + where: { + id: req.user_id, + }, + relations: ["guild", "roles", "user"], + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + // prettier-ignore + select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"], + }); - const guildsWithPermissions = guilds.map((x) => { - const perms = Permissions.finalPermission({ - user: { - id: user.id, - roles: x.roles?.map((x) => x.id) || [], - communication_disabled_until: x.communication_disabled_until, - flags: x.user.flags, - }, - guild: { - roles: x?.roles || [], - id: x.guild.id, - owner_id: x.guild.owner_id!, // ownerless guilds...? - }, - }); + const guildsWithPermissions = guilds.map((x) => { + const perms = Permissions.finalPermission({ + user: { + id: user.id, + roles: x.roles?.map((x) => x.id) || [], + communication_disabled_until: x.communication_disabled_until, + flags: x.user.flags, + }, + guild: { + roles: x?.roles || [], + id: x.guild.id, + owner_id: x.guild.owner_id!, // ownerless guilds...? + }, + }); - return { - id: x.guild.id, - name: x.guild.name, - icon: x.guild.icon, - mfa_level: x.guild.mfa_level, - permissions: perms.bitfield.toString(), - }; - }); + return { + id: x.guild.id, + name: x.guild.name, + icon: x.guild.icon, + mfa_level: x.guild.mfa_level, + permissions: perms.bitfield.toString(), + }; + }); - return res.json({ - guilds: guildsWithPermissions, - user: { - id: user.id, - username: user.username, - avatar: user.avatar, - avatar_decoration: null, // TODO - discriminator: user.discriminator, - public_flags: user.public_flags, - }, - application: { - id: app.id, - name: app.name, - icon: app.icon, - description: app.description, - summary: app.summary, - type: app.type, - hook: app.hook, - guild_id: null, // TODO support guilds - bot_public: app.bot_public, - bot_require_code_grant: app.bot_require_code_grant, - verify_key: app.verify_key, - flags: app.flags, - }, - bot: { - id: bot.id, - username: bot.username, - avatar: bot.avatar, - avatar_decoration: null, // TODO - discriminator: bot.discriminator, - public_flags: bot.public_flags, - bot: true, - approximated_guild_count: 0, // TODO - }, - authorized: false, - }); - }, + return res.json({ + guilds: guildsWithPermissions, + user: { + id: user.id, + username: user.username, + avatar: user.avatar, + avatar_decoration: null, // TODO + discriminator: user.discriminator, + public_flags: user.public_flags, + }, + application: { + id: app.id, + name: app.name, + icon: app.icon, + description: app.description, + summary: app.summary, + type: app.type, + hook: app.hook, + guild_id: null, // TODO support guilds + bot_public: app.bot_public, + bot_require_code_grant: app.bot_require_code_grant, + verify_key: app.verify_key, + flags: app.flags, + }, + bot: { + id: bot.id, + username: bot.username, + avatar: bot.avatar, + avatar_decoration: null, // TODO + discriminator: bot.discriminator, + public_flags: bot.public_flags, + bot: true, + approximated_guild_count: 0, // TODO + }, + authorized: false, + }); + }, ); router.post( - "/", - route({ - requestBody: "ApplicationAuthorizeSchema", - query: { - client_id: { - type: "string", - }, - }, - responses: { - 200: { - body: "OAuthAuthorizeResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as ApplicationAuthorizeSchema; - // const { client_id, scope, response_type, redirect_url } = req.query; - const { client_id } = req.query; + "/", + route({ + requestBody: "ApplicationAuthorizeSchema", + query: { + client_id: { + type: "string", + }, + }, + responses: { + 200: { + body: "OAuthAuthorizeResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationAuthorizeSchema; + // const { client_id, scope, response_type, redirect_url } = req.query; + const { client_id } = req.query; - if (!client_id) { - throw FieldErrors({ - client_id: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + if (!client_id) { + throw FieldErrors({ + client_id: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - // TODO: ensure guild_id is not an empty string - // TODO: captcha verification - // TODO: MFA verification + // TODO: ensure guild_id is not an empty string + // TODO: captcha verification + // TODO: MFA verification - const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] }); - // getPermission cache won't exist if we're owner - if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED; - perms.hasThrow("MANAGE_GUILD"); + const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] }); + // getPermission cache won't exist if we're owner + if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED; + perms.hasThrow("MANAGE_GUILD"); - const app = await Application.findOne({ - where: { - id: client_id as string, - }, - relations: ["bot"], - }); + const app = await Application.findOne({ + where: { + id: client_id as string, + }, + relations: ["bot"], + }); - // TODO: use DiscordApiErrors - // findOneOrFail throws code 404 - if (!app) throw new ApiError("Unknown Application", 10002, 404); - if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400); + // TODO: use DiscordApiErrors + // findOneOrFail throws code 404 + if (!app) throw new ApiError("Unknown Application", 10002, 404); + if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400); - await Member.addToGuild(app.id, body.guild_id); + await Member.addToGuild(app.id, body.guild_id); - return res.json({ - location: "/oauth2/authorized", // redirect URL - }); - }, + return res.json({ + location: "/oauth2/authorized", // redirect URL + }); + }, ); export default router; diff --git a/src/api/routes/oauth2/tokens.ts b/src/api/routes/oauth2/tokens.ts index 6b85bab..4af1061 100644 --- a/src/api/routes/oauth2/tokens.ts +++ b/src/api/routes/oauth2/tokens.ts @@ -21,8 +21,8 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([]); + //TODO + res.json([]); }); export default router; diff --git a/src/api/routes/outbound-promotions.ts b/src/api/routes/outbound-promotions.ts index f1f9a29..e8e6f65 100644 --- a/src/api/routes/outbound-promotions.ts +++ b/src/api/routes/outbound-promotions.ts @@ -22,8 +22,8 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([]).status(200); + //TODO + res.json([]).status(200); }); export default router; diff --git "a/src/api/routes/partners/\043guild_id/requirements.ts" "b/src/api/routes/partners/\043guild_id/requirements.ts" index 30ec8b3..9a08bc6 100644 --- "a/src/api/routes/partners/\043guild_id/requirements.ts" +++ "b/src/api/routes/partners/\043guild_id/requirements.ts" @@ -22,34 +22,34 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id } = req.params; - // TODO: - // Load from database - // Admin control, but for now it allows anyone to be discoverable + const { guild_id } = req.params; + // TODO: + // Load from database + // Admin control, but for now it allows anyone to be discoverable - res.send({ - guild_id: guild_id, - safe_environment: true, - healthy: true, - health_score_pending: false, - size: true, - nsfw_properties: {}, - protected: true, - sufficient: true, - sufficient_without_grace_period: true, - valid_rules_channel: true, - retention_healthy: true, - engagement_healthy: true, - age: true, - minimum_age: 0, - health_score: { - avg_nonnew_participators: 0, - avg_nonnew_communicators: 0, - num_intentful_joiners: 0, - perc_ret_w1_intentful: 0, - }, - minimum_size: 0, - }); + res.send({ + guild_id: guild_id, + safe_environment: true, + healthy: true, + health_score_pending: false, + size: true, + nsfw_properties: {}, + protected: true, + sufficient: true, + sufficient_without_grace_period: true, + valid_rules_channel: true, + retention_healthy: true, + engagement_healthy: true, + age: true, + minimum_age: 0, + health_score: { + avg_nonnew_participators: 0, + avg_nonnew_communicators: 0, + num_intentful_joiners: 0, + perc_ret_w1_intentful: 0, + }, + minimum_size: 0, + }); }); export default router; diff --git a/src/api/routes/ping.ts b/src/api/routes/ping.ts index 55e4b7d..be737f8 100644 --- a/src/api/routes/ping.ts +++ b/src/api/routes/ping.ts @@ -23,32 +23,32 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "InstancePingResponse", - }, - }, - }), - (req: Request, res: Response) => { - const { general } = Config.get(); - res.send({ - ping: "pong!", - instance: { - id: general.instanceId, - name: general.instanceName, - description: general.instanceDescription, - image: general.image, + "/", + route({ + responses: { + 200: { + body: "InstancePingResponse", + }, + }, + }), + (req: Request, res: Response) => { + const { general } = Config.get(); + res.send({ + ping: "pong!", + instance: { + id: general.instanceId, + name: general.instanceName, + description: general.instanceDescription, + image: general.image, - correspondenceEmail: general.correspondenceEmail, - correspondenceUserID: general.correspondenceUserID, + correspondenceEmail: general.correspondenceEmail, + correspondenceUserID: general.correspondenceUserID, - frontPage: general.frontPage, - tosPage: general.tosPage, - }, - }); - }, + frontPage: general.frontPage, + tosPage: general.tosPage, + }, + }); + }, ); export default router; diff --git a/src/api/routes/policies/instance/config.ts b/src/api/routes/policies/instance/config.ts index 97fb509..946ac8a 100755 --- a/src/api/routes/policies/instance/config.ts +++ b/src/api/routes/policies/instance/config.ts @@ -23,44 +23,44 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Object", - }, - }, - }), - async (req: Request, res: Response) => { - const general = Config.get(); - let outputtedConfig; - if (req.user_id) { - const rights = await getRights(req.user_id); - if (rights.has("OPERATOR")) outputtedConfig = general; - } else { - outputtedConfig = { - limits_user_maxGuilds: general.limits.user.maxGuilds, - limits_user_maxBio: general.limits.user.maxBio, - limits_guild_maxEmojis: general.limits.guild.maxEmojis, - limits_guild_maxRoles: general.limits.guild.maxRoles, - limits_message_maxCharacters: general.limits.message.maxCharacters, - limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize, - limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize, - limits_channel_maxWebhooks: general.limits.channel.maxWebhooks, - register_dateOfBirth_requiredc: general.register.dateOfBirth.required, - register_password_required: general.register.password.required, - register_disabled: general.register.disabled, - register_requireInvite: general.register.requireInvite, - register_allowNewRegistration: general.register.allowNewRegistration, - register_allowMultipleAccounts: general.register.allowMultipleAccounts, - guild_autoJoin_canLeave: general.guild.autoJoin.canLeave, - guild_autoJoin_guilds_x: general.guild.autoJoin.guilds, - register_email_required: general.register.email.required, - can_recover_account: general.email.provider != null && general.general.frontPage != null, - }; - } - res.send(outputtedConfig); - }, + "/", + route({ + responses: { + 200: { + body: "Object", + }, + }, + }), + async (req: Request, res: Response) => { + const general = Config.get(); + let outputtedConfig; + if (req.user_id) { + const rights = await getRights(req.user_id); + if (rights.has("OPERATOR")) outputtedConfig = general; + } else { + outputtedConfig = { + limits_user_maxGuilds: general.limits.user.maxGuilds, + limits_user_maxBio: general.limits.user.maxBio, + limits_guild_maxEmojis: general.limits.guild.maxEmojis, + limits_guild_maxRoles: general.limits.guild.maxRoles, + limits_message_maxCharacters: general.limits.message.maxCharacters, + limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize, + limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize, + limits_channel_maxWebhooks: general.limits.channel.maxWebhooks, + register_dateOfBirth_requiredc: general.register.dateOfBirth.required, + register_password_required: general.register.password.required, + register_disabled: general.register.disabled, + register_requireInvite: general.register.requireInvite, + register_allowNewRegistration: general.register.allowNewRegistration, + register_allowMultipleAccounts: general.register.allowMultipleAccounts, + guild_autoJoin_canLeave: general.guild.autoJoin.canLeave, + guild_autoJoin_guilds_x: general.guild.autoJoin.guilds, + register_email_required: general.register.email.required, + can_recover_account: general.email.provider != null && general.general.frontPage != null, + }; + } + res.send(outputtedConfig); + }, ); export default router; diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts index 967ac2f..7aaaeb5 100644 --- a/src/api/routes/policies/instance/domains.ts +++ b/src/api/routes/policies/instance/domains.ts @@ -22,26 +22,26 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "InstanceDomainsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { cdn, gateway, api } = Config.get(); + "/", + route({ + responses: { + 200: { + body: "InstanceDomainsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { cdn, gateway, api } = Config.get(); - res.json({ - admin: Config.get().admin.endpointPublic, - api: Config.get().api.endpointPublic?.split("/api")[0] || "", // Transitional, see /.well-known/spacebar/client - apiEndpoint: api.endpointPublic, - cdn: cdn.endpointPublic, - defaultApiVersion: api.defaultVersion, - gateway: gateway.endpointPublic, - }); - }, + res.json({ + admin: Config.get().admin.endpointPublic, + api: Config.get().api.endpointPublic?.split("/api")[0] || "", // Transitional, see /.well-known/spacebar/client + apiEndpoint: api.endpointPublic, + cdn: cdn.endpointPublic, + defaultApiVersion: api.defaultVersion, + gateway: gateway.endpointPublic, + }); + }, ); export default router; diff --git a/src/api/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts index 8fc214f..a54570f 100644 --- a/src/api/routes/policies/instance/index.ts +++ b/src/api/routes/policies/instance/index.ts @@ -22,18 +22,18 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIGeneralConfiguration", - }, - }, - }), - async (req: Request, res: Response) => { - const { general } = Config.get(); - res.json(general); - }, + "/", + route({ + responses: { + 200: { + body: "APIGeneralConfiguration", + }, + }, + }), + async (req: Request, res: Response) => { + const { general } = Config.get(); + res.json(general); + }, ); export default router; diff --git a/src/api/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts index 0d0ed32..7ade151 100644 --- a/src/api/routes/policies/instance/limits.ts +++ b/src/api/routes/policies/instance/limits.ts @@ -22,18 +22,18 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APILimitsConfiguration", - }, - }, - }), - async (req: Request, res: Response) => { - const { limits } = Config.get(); - res.json(limits); - }, + "/", + route({ + responses: { + 200: { + body: "APILimitsConfiguration", + }, + }, + }), + async (req: Request, res: Response) => { + const { limits } = Config.get(); + res.json(limits); + }, ); export default router; diff --git a/src/api/routes/policies/stats.ts b/src/api/routes/policies/stats.ts index 0828c37..0edcfd6 100644 --- a/src/api/routes/policies/stats.ts +++ b/src/api/routes/policies/stats.ts @@ -22,32 +22,32 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "InstanceStatsResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - if (!Config.get().security.statsWorldReadable) { - const rights = await getRights(req.user_id); - rights.hasThrow("VIEW_SERVER_STATS"); - } + "/", + route({ + responses: { + 200: { + body: "InstanceStatsResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + if (!Config.get().security.statsWorldReadable) { + const rights = await getRights(req.user_id); + rights.hasThrow("VIEW_SERVER_STATS"); + } - res.json({ - counts: { - user: await User.count(), - guild: await Guild.count(), - message: await Message.count(), - members: await Member.count(), - }, - }); - }, + res.json({ + counts: { + user: await User.count(), + guild: await Guild.count(), + message: await Message.count(), + members: await Member.count(), + }, + }); + }, ); export default router; diff --git a/src/api/routes/read-states/ack-bulk.ts b/src/api/routes/read-states/ack-bulk.ts index a597e4b..d0212c0 100644 --- a/src/api/routes/read-states/ack-bulk.ts +++ b/src/api/routes/read-states/ack-bulk.ts @@ -23,48 +23,48 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "AckBulkSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as AckBulkSchema; + "/", + route({ + requestBody: "AckBulkSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as AckBulkSchema; - // TODO: what is read_state_type ? + // TODO: what is read_state_type ? - await Promise.all([ - // for every new state - ...body.read_states.map(async (x) => { - // find an existing one - const ret = - (await ReadState.findOne({ - where: { - user_id: req.user_id, - channel_id: x.channel_id, - }, - })) ?? - // if it doesn't exist, create it (not a promise) - ReadState.create({ - user_id: req.user_id, - channel_id: x.channel_id, - }); + await Promise.all([ + // for every new state + ...body.read_states.map(async (x) => { + // find an existing one + const ret = + (await ReadState.findOne({ + where: { + user_id: req.user_id, + channel_id: x.channel_id, + }, + })) ?? + // if it doesn't exist, create it (not a promise) + ReadState.create({ + user_id: req.user_id, + channel_id: x.channel_id, + }); - ret.last_message_id = x.message_id; - //It's a little more complicated than this but this'll do - ret.mention_count = 0; + ret.last_message_id = x.message_id; + //It's a little more complicated than this but this'll do + ret.mention_count = 0; - return ret.save(); - }), - ]); + return ret.save(); + }), + ]); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/reporting/menu/message.ts b/src/api/routes/reporting/menu/message.ts index c72bc84..de57c79 100644 --- a/src/api/routes/reporting/menu/message.ts +++ b/src/api/routes/reporting/menu/message.ts @@ -22,19 +22,19 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "ReportingMenuResponse", - }, - 204: {}, - }, - }), - (req: Request, res: Response) => { - // TODO: implement - //res.send([] as ReportingMenuResponseSchema); - }, + "/", + route({ + responses: { + 200: { + body: "ReportingMenuResponse", + }, + 204: {}, + }, + }), + (req: Request, res: Response) => { + // TODO: implement + //res.send([] as ReportingMenuResponseSchema); + }, ); export default router; diff --git a/src/api/routes/safety-hub/@me/index.ts b/src/api/routes/safety-hub/@me/index.ts index 06a1e28..7d0e7ff 100644 --- a/src/api/routes/safety-hub/@me/index.ts +++ b/src/api/routes/safety-hub/@me/index.ts @@ -24,36 +24,36 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "AccountStandingResponse", - }, - 401: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); + "/", + route({ + responses: { + 200: { + body: "AccountStandingResponse", + }, + 401: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); - res.send({ - classifications: [], - guild_classifications: [], - account_standing: { - state: AccountStandingState.ALL_GOOD, - }, - is_dsa_eligible: true, - username: user.username, - discriminator: user.discriminator, - is_appeal_eligible: true, - appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE], - } as AccountStandingResponse); - }, + res.send({ + classifications: [], + guild_classifications: [], + account_standing: { + state: AccountStandingState.ALL_GOOD, + }, + is_dsa_eligible: true, + username: user.username, + discriminator: user.discriminator, + is_appeal_eligible: true, + appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE], + } as AccountStandingResponse); + }, ); export default router; diff --git a/src/api/routes/safety-hub/suspended/@me.ts b/src/api/routes/safety-hub/suspended/@me.ts index 06a1e28..7d0e7ff 100644 --- a/src/api/routes/safety-hub/suspended/@me.ts +++ b/src/api/routes/safety-hub/suspended/@me.ts @@ -24,36 +24,36 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "AccountStandingResponse", - }, - 401: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); + "/", + route({ + responses: { + 200: { + body: "AccountStandingResponse", + }, + 401: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); - res.send({ - classifications: [], - guild_classifications: [], - account_standing: { - state: AccountStandingState.ALL_GOOD, - }, - is_dsa_eligible: true, - username: user.username, - discriminator: user.discriminator, - is_appeal_eligible: true, - appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE], - } as AccountStandingResponse); - }, + res.send({ + classifications: [], + guild_classifications: [], + account_standing: { + state: AccountStandingState.ALL_GOOD, + }, + is_dsa_eligible: true, + username: user.username, + discriminator: user.discriminator, + is_appeal_eligible: true, + appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE], + } as AccountStandingResponse); + }, ); export default router; diff --git a/src/api/routes/scheduled-maintenances/upcoming.json.ts b/src/api/routes/scheduled-maintenances/upcoming.json.ts index de4dcbf..5958d58 100644 --- a/src/api/routes/scheduled-maintenances/upcoming.json.ts +++ b/src/api/routes/scheduled-maintenances/upcoming.json.ts @@ -21,10 +21,10 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - res.json({ - page: {}, - scheduled_maintenances: {}, - }); + res.json({ + page: {}, + scheduled_maintenances: {}, + }); }); export default router; diff --git a/src/api/routes/science.ts b/src/api/routes/science.ts index 2d2d519..4a6ddea 100644 --- a/src/api/routes/science.ts +++ b/src/api/routes/science.ts @@ -22,16 +22,16 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - responses: { - 204: {}, - }, - }), - (req: Request, res: Response) => { - // TODO: - res.sendStatus(204); - }, + "/", + route({ + responses: { + 204: {}, + }, + }), + (req: Request, res: Response) => { + // TODO: + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/stage-instances.ts b/src/api/routes/stage-instances.ts index f1f9a29..e8e6f65 100644 --- a/src/api/routes/stage-instances.ts +++ b/src/api/routes/stage-instances.ts @@ -22,8 +22,8 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([]).status(200); + //TODO + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/sticker-packs/index.ts b/src/api/routes/sticker-packs/index.ts index 7d2fe17..749ce02 100644 --- a/src/api/routes/sticker-packs/index.ts +++ b/src/api/routes/sticker-packs/index.ts @@ -23,21 +23,21 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIStickerPackArray", - }, - }, - }), - async (req: Request, res: Response) => { - const sticker_packs = await StickerPack.find({ - relations: ["stickers"], - }); + "/", + route({ + responses: { + 200: { + body: "APIStickerPackArray", + }, + }, + }), + async (req: Request, res: Response) => { + const sticker_packs = await StickerPack.find({ + relations: ["stickers"], + }); - res.json({ sticker_packs }); - }, + res.json({ sticker_packs }); + }, ); export default router; diff --git "a/src/api/routes/stickers/\043sticker_id/index.ts" "b/src/api/routes/stickers/\043sticker_id/index.ts" index ddbc397..0f850ef 100644 --- "a/src/api/routes/stickers/\043sticker_id/index.ts" +++ "b/src/api/routes/stickers/\043sticker_id/index.ts" @@ -22,19 +22,19 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Sticker", - }, - }, - }), - async (req: Request, res: Response) => { - const { sticker_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "Sticker", + }, + }, + }), + async (req: Request, res: Response) => { + const { sticker_id } = req.params; - res.json(await Sticker.find({ where: { id: sticker_id } })); - }, + res.json(await Sticker.find({ where: { id: sticker_id } })); + }, ); export default router; diff --git a/src/api/routes/stop.ts b/src/api/routes/stop.ts index bf4af56..be29e0a 100644 --- a/src/api/routes/stop.ts +++ b/src/api/routes/stop.ts @@ -22,21 +22,21 @@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - right: "OPERATOR", - responses: { - 200: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - console.log(`/stop was called by ${req.user_id} at ${new Date()}`); - res.sendStatus(200); - process.kill(process.pid, "SIGTERM"); - }, + "/", + route({ + right: "OPERATOR", + responses: { + 200: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + console.log(`/stop was called by ${req.user_id} at ${new Date()}`); + res.sendStatus(200); + process.kill(process.pid, "SIGTERM"); + }, ); export default router; diff --git "a/src/api/routes/store/published-listings/applications/\043application_id/index.ts" "b/src/api/routes/store/published-listings/applications/\043application_id/index.ts" index eb2df64..39fa9db 100644 --- "a/src/api/routes/store/published-listings/applications/\043application_id/index.ts" +++ "b/src/api/routes/store/published-listings/applications/\043application_id/index.ts" @@ -22,76 +22,76 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - // const id = req.params.id; - res.json({ - id: "", - summary: "", - sku: { - id: "", - type: 1, - dependent_sku_id: null, - application_id: "", - manifest_labels: [], - access_type: 2, - name: "", - features: [], - release_date: "", - premium: false, - slug: "", - flags: 4, - genres: [], - legal_notice: "", - application: { - id: "", - name: "", - icon: "", - description: "", - summary: "", - cover_image: "", - primary_sku_id: "", - hook: true, - slug: "", - guild_id: "", - bot_public: "", - bot_require_code_grant: false, - verify_key: "", - publishers: [ - { - id: "", - name: "", - }, - ], - developers: [ - { - id: "", - name: "", - }, - ], - system_requirements: {}, - show_age_gate: false, - price: { - amount: 0, - currency: "EUR", - }, - locales: [], - }, - tagline: "", - description: "", - carousel_items: [ - { - asset_id: "", - }, - ], - header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160} - header_logo_light_theme: {}, - box_art: {}, - thumbnail: {}, - header_background: {}, - hero_background: {}, - assets: [], - }, - }).status(200); + //TODO + // const id = req.params.id; + res.json({ + id: "", + summary: "", + sku: { + id: "", + type: 1, + dependent_sku_id: null, + application_id: "", + manifest_labels: [], + access_type: 2, + name: "", + features: [], + release_date: "", + premium: false, + slug: "", + flags: 4, + genres: [], + legal_notice: "", + application: { + id: "", + name: "", + icon: "", + description: "", + summary: "", + cover_image: "", + primary_sku_id: "", + hook: true, + slug: "", + guild_id: "", + bot_public: "", + bot_require_code_grant: false, + verify_key: "", + publishers: [ + { + id: "", + name: "", + }, + ], + developers: [ + { + id: "", + name: "", + }, + ], + system_requirements: {}, + show_age_gate: false, + price: { + amount: 0, + currency: "EUR", + }, + locales: [], + }, + tagline: "", + description: "", + carousel_items: [ + { + asset_id: "", + }, + ], + header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160} + header_logo_light_theme: {}, + box_art: {}, + thumbnail: {}, + header_background: {}, + hero_background: {}, + assets: [], + }, + }).status(200); }); export default router; diff --git "a/src/api/routes/store/published-listings/applications/\043application_id/subscription-plans.ts" "b/src/api/routes/store/published-listings/applications/\043application_id/subscription-plans.ts" index b8f5963..953e391 100644 --- "a/src/api/routes/store/published-listings/applications/\043application_id/subscription-plans.ts" +++ "b/src/api/routes/store/published-listings/applications/\043application_id/subscription-plans.ts" @@ -22,22 +22,22 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([ - { - id: "", - name: "", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "", - fallback_price: 499, - fallback_currency: "eur", - currency: "eur", - price: 4199, - price_tier: null, - }, - ]).status(200); + //TODO + res.json([ + { + id: "", + name: "", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "", + fallback_price: 499, + fallback_currency: "eur", + currency: "eur", + price: 4199, + price_tier: null, + }, + ]).status(200); }); export default router; diff --git a/src/api/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts index a463b4e..03cbab4 100644 --- a/src/api/routes/store/published-listings/skus.ts +++ b/src/api/routes/store/published-listings/skus.ts @@ -22,76 +22,76 @@ const router: Router = Router({ mergeParams: true }); router.get("/:sku_id", route({}), async (req: Request, res: Response) => { - //TODO - // const id = req.params.id; - res.json({ - id: "", - summary: "", - sku: { - id: "", - type: 1, - dependent_sku_id: null, - application_id: "", - manifets_labels: [], - access_type: 2, - name: "", - features: [], - release_date: "", - premium: false, - slug: "", - flags: 4, - genres: [], - legal_notice: "", - application: { - id: "", - name: "", - icon: "", - description: "", - summary: "", - cover_image: "", - primary_sku_id: "", - hook: true, - slug: "", - guild_id: "", - bot_public: "", - bot_require_code_grant: false, - verify_key: "", - publishers: [ - { - id: "", - name: "", - }, - ], - developers: [ - { - id: "", - name: "", - }, - ], - system_requirements: {}, - show_age_gate: false, - price: { - amount: 0, - currency: "EUR", - }, - locales: [], - }, - tagline: "", - description: "", - carousel_items: [ - { - asset_id: "", - }, - ], - header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160} - header_logo_light_theme: {}, - box_art: {}, - thumbnail: {}, - header_background: {}, - hero_background: {}, - assets: [], - }, - }).status(200); + //TODO + // const id = req.params.id; + res.json({ + id: "", + summary: "", + sku: { + id: "", + type: 1, + dependent_sku_id: null, + application_id: "", + manifets_labels: [], + access_type: 2, + name: "", + features: [], + release_date: "", + premium: false, + slug: "", + flags: 4, + genres: [], + legal_notice: "", + application: { + id: "", + name: "", + icon: "", + description: "", + summary: "", + cover_image: "", + primary_sku_id: "", + hook: true, + slug: "", + guild_id: "", + bot_public: "", + bot_require_code_grant: false, + verify_key: "", + publishers: [ + { + id: "", + name: "", + }, + ], + developers: [ + { + id: "", + name: "", + }, + ], + system_requirements: {}, + show_age_gate: false, + price: { + amount: 0, + currency: "EUR", + }, + locales: [], + }, + tagline: "", + description: "", + carousel_items: [ + { + asset_id: "", + }, + ], + header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160} + header_logo_light_theme: {}, + box_art: {}, + thumbnail: {}, + header_background: {}, + hero_background: {}, + assets: [], + }, + }).status(200); }); export default router; diff --git "a/src/api/routes/store/published-listings/skus/\043sku_id/subscription-plans.ts" "b/src/api/routes/store/published-listings/skus/\043sku_id/subscription-plans.ts" index f2ea23c..0aae149 100644 --- "a/src/api/routes/store/published-listings/skus/\043sku_id/subscription-plans.ts" +++ "b/src/api/routes/store/published-listings/skus/\043sku_id/subscription-plans.ts" @@ -22,310 +22,310 @@ const router: Router = Router({ mergeParams: true }); const skus = new Map([ - [ - "521842865731534868", - [ - { - id: "511651856145973248", - name: "Individual Premium Tier 3 Monthly (Legacy)", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "521842865731534868", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "511651860671627264", - name: "Individiual Premium Tier 3 Yearly (Legacy)", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "521842865731534868", - currency: "eur", - price: 0, - price_tier: null, - }, - ], - ], - [ - "521846918637420545", - [ - { - id: "511651871736201216", - name: "Individual Premium Tier 2 Monthly", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "521846918637420545", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "511651876987469824", - name: "Individual Premum Tier 2 Yearly", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "521846918637420545", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "978380684370378761", - name: "Individual Premum Tier 1", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "521846918637420545", - currency: "eur", - price: 0, - price_tier: null, - }, - ], - ], - [ - "521847234246082599", - [ - { - id: "642251038925127690", - name: "Individual Premium Tier 3 Quarterly", - interval: 1, - interval_count: 3, - tax_inclusive: true, - sku_id: "521847234246082599", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "511651880837840896", - name: "Individual Premium Tier 3 Monthly", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "521847234246082599", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "511651885459963904", - name: "Individual Premium Tier 3 Yearly", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "521847234246082599", - currency: "eur", - price: 0, - price_tier: null, - }, - ], - ], - [ - "590663762298667008", - [ - { - id: "590665532894740483", - name: "Crowd Premium Monthly", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "590663762298667008", - discount_price: 0, - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "590665538238152709", - name: "Crowd Premium Yearly", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "590663762298667008", - discount_price: 0, - currency: "eur", - price: 0, - price_tier: null, - }, - ], - ], - [ - "978380684370378762", - [ - [ - { - id: "978380692553465866", - name: "Premium Tier 0 Monthly", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "978380684370378762", - currency: "usd", - price: 299, - price_tier: null, - prices: { - "0": { - country_prices: { - country_code: "US", - prices: [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - payment_source_prices: { - "775487223059316758": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "736345864146255982": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "683074999590060249": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - }, - "3": { - country_prices: { - country_code: "US", - prices: [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - payment_source_prices: { - "775487223059316758": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "736345864146255982": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "683074999590060249": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - }, - "4": { - country_prices: { - country_code: "US", - prices: [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - payment_source_prices: { - "775487223059316758": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "736345864146255982": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "683074999590060249": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - }, - "1": { - country_prices: { - country_code: "US", - prices: [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - payment_source_prices: { - "775487223059316758": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "736345864146255982": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "683074999590060249": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - }, - }, - }, - ], - ], - ], + [ + "521842865731534868", + [ + { + id: "511651856145973248", + name: "Individual Premium Tier 3 Monthly (Legacy)", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521842865731534868", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "511651860671627264", + name: "Individiual Premium Tier 3 Yearly (Legacy)", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521842865731534868", + currency: "eur", + price: 0, + price_tier: null, + }, + ], + ], + [ + "521846918637420545", + [ + { + id: "511651871736201216", + name: "Individual Premium Tier 2 Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "511651876987469824", + name: "Individual Premum Tier 2 Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "978380684370378761", + name: "Individual Premum Tier 1", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null, + }, + ], + ], + [ + "521847234246082599", + [ + { + id: "642251038925127690", + name: "Individual Premium Tier 3 Quarterly", + interval: 1, + interval_count: 3, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "511651880837840896", + name: "Individual Premium Tier 3 Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "511651885459963904", + name: "Individual Premium Tier 3 Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null, + }, + ], + ], + [ + "590663762298667008", + [ + { + id: "590665532894740483", + name: "Crowd Premium Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "590663762298667008", + discount_price: 0, + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "590665538238152709", + name: "Crowd Premium Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "590663762298667008", + discount_price: 0, + currency: "eur", + price: 0, + price_tier: null, + }, + ], + ], + [ + "978380684370378762", + [ + [ + { + id: "978380692553465866", + name: "Premium Tier 0 Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "978380684370378762", + currency: "usd", + price: 299, + price_tier: null, + prices: { + "0": { + country_prices: { + country_code: "US", + prices: [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + payment_source_prices: { + "775487223059316758": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "736345864146255982": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "683074999590060249": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + }, + "3": { + country_prices: { + country_code: "US", + prices: [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + payment_source_prices: { + "775487223059316758": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "736345864146255982": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "683074999590060249": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + }, + "4": { + country_prices: { + country_code: "US", + prices: [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + payment_source_prices: { + "775487223059316758": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "736345864146255982": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "683074999590060249": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + }, + "1": { + country_prices: { + country_code: "US", + prices: [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + payment_source_prices: { + "775487223059316758": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "736345864146255982": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "683074999590060249": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + }, + }, + }, + ], + ], + ], ]); router.get("/", route({}), async (req: Request, res: Response) => { - // TODO: add the ability to add custom - const { sku_id } = req.params; + // TODO: add the ability to add custom + const { sku_id } = req.params; - if (!skus.has(sku_id)) { - console.log(`Request for invalid SKU ${sku_id}! Please report this!`); - res.sendStatus(404); - } else { - res.json(skus.get(sku_id)).status(200); - } + if (!skus.has(sku_id)) { + console.log(`Request for invalid SKU ${sku_id}! Please report this!`); + res.sendStatus(404); + } else { + res.json(skus.get(sku_id)).status(200); + } }); export default router; diff --git a/src/api/routes/teams.ts b/src/api/routes/teams.ts index 21ec3a5..27ccabf 100644 --- a/src/api/routes/teams.ts +++ b/src/api/routes/teams.ts @@ -25,67 +25,67 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - query: { - include_payout_account_status: { - type: "boolean", - description: "Whether to include team payout account status in the response (default false)", - }, - }, - responses: { - 200: { - body: "TeamListResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const teams = await Team.find({ - where: { - owner_user_id: req.user_id, - }, - relations: ["members"], - }); + "/", + route({ + query: { + include_payout_account_status: { + type: "boolean", + description: "Whether to include team payout account status in the response (default false)", + }, + }, + responses: { + 200: { + body: "TeamListResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const teams = await Team.find({ + where: { + owner_user_id: req.user_id, + }, + relations: ["members"], + }); - res.send(teams); - }, + res.send(teams); + }, ); router.post( - "/", - route({ - requestBody: "TeamCreateSchema", - responses: { - 200: { - body: "Team", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: [{ id: req.user_id }], - select: ["mfa_enabled"], - }); - if (!user.mfa_enabled) throw new HTTPError("You must enable MFA to create a team"); + "/", + route({ + requestBody: "TeamCreateSchema", + responses: { + 200: { + body: "Team", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: [{ id: req.user_id }], + select: ["mfa_enabled"], + }); + if (!user.mfa_enabled) throw new HTTPError("You must enable MFA to create a team"); - const body = req.body as TeamCreateSchema; + const body = req.body as TeamCreateSchema; - const team = Team.create({ - name: body.name, - owner_user_id: req.user_id, - }); - await team.save(); + const team = Team.create({ + name: body.name, + owner_user_id: req.user_id, + }); + await team.save(); - await TeamMember.create({ - user_id: req.user_id, - team_id: team.id, - membership_state: TeamMemberState.ACCEPTED, - permissions: ["*"], - role: TeamMemberRole.ADMIN, - }).save(); + await TeamMember.create({ + user_id: req.user_id, + team_id: team.id, + membership_state: TeamMemberState.ACCEPTED, + permissions: ["*"], + role: TeamMemberRole.ADMIN, + }).save(); - res.json(team); - }, + res.json(team); + }, ); export default router; diff --git a/src/api/routes/track.ts b/src/api/routes/track.ts index 644302c..d320613 100644 --- a/src/api/routes/track.ts +++ b/src/api/routes/track.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.post("/", route({}), (req: Request, res: Response) => { - // TODO: - res.sendStatus(204); + // TODO: + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts index 5262dc2..7228a76 100644 --- a/src/api/routes/updates.ts +++ b/src/api/routes/updates.ts @@ -23,46 +23,46 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "UpdatesResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const platform = req.query.platform; + "/", + route({ + responses: { + 200: { + body: "UpdatesResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const platform = req.query.platform; - if (!platform) - throw FieldErrors({ - platform: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); + if (!platform) + throw FieldErrors({ + platform: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); - const release = await ClientRelease.findOneOrFail({ - where: { - enabled: true, - platform: platform as string, - }, - order: { pub_date: "DESC" }, - }); + const release = await ClientRelease.findOneOrFail({ + where: { + enabled: true, + platform: platform as string, + }, + order: { pub_date: "DESC" }, + }); - res.json({ - name: release.name, - pub_date: release.pub_date, - url: release.url, - notes: release.notes, - }); - }, + res.json({ + name: release.name, + pub_date: release.pub_date, + url: release.url, + notes: release.notes, + }); + }, ); export default router; diff --git "a/src/api/routes/users/\043user_id/delete.ts" "b/src/api/routes/users/\043user_id/delete.ts" index be361f1..c2286ce 100644 --- "a/src/api/routes/users/\043user_id/delete.ts" +++ "b/src/api/routes/users/\043user_id/delete.ts" @@ -18,20 +18,20 @@ import { route } from "@spacebar/api"; import { - Channel, - ChannelDeleteEvent, - ChannelRecipientRemoveEvent, - emitEvent, - Emoji, - Guild, - InstanceBan, - Member, - Recipient, - Sticker, - Stopwatch, - User, - UserDeleteEvent, - UserSettingsProtos, + Channel, + ChannelDeleteEvent, + ChannelRecipientRemoveEvent, + emitEvent, + Emoji, + Guild, + InstanceBan, + Member, + Recipient, + Sticker, + Stopwatch, + User, + UserDeleteEvent, + UserSettingsProtos, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { ChannelType, InstanceUserDeleteSchema, PrivateUserProjection } from "@spacebar/schemas"; @@ -40,158 +40,158 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - right: "MANAGE_USERS", - requestBody: "InstanceUserDeleteSchema", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const sw = Stopwatch.startNew(); - const body = req.body as InstanceUserDeleteSchema | undefined; - const user = await User.findOneOrFail({ - where: { id: req.params.user_id }, - select: [...PrivateUserProjection, "data"], - }); + "/", + route({ + right: "MANAGE_USERS", + requestBody: "InstanceUserDeleteSchema", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const sw = Stopwatch.startNew(); + const body = req.body as InstanceUserDeleteSchema | undefined; + const user = await User.findOneOrFail({ + where: { id: req.params.user_id }, + select: [...PrivateUserProjection, "data"], + }); - if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } }))) - await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "" }).save(); + if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } }))) + await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "" }).save(); - // prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow - const dmChannels = await user.getDmChannels(); - for (const channel of dmChannels) { - console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`); - await emitEvent({ - event: "CHANNEL_DELETE", - data: channel.toJSON(), - channel_id: channel.id, - } as ChannelDeleteEvent); - await Recipient.delete({ channel_id: channel.id }); - await Channel.deleteChannel(channel); - } + // prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow + const dmChannels = await user.getDmChannels(); + for (const channel of dmChannels) { + console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`); + await emitEvent({ + event: "CHANNEL_DELETE", + data: channel.toJSON(), + channel_id: channel.id, + } as ChannelDeleteEvent); + await Recipient.delete({ channel_id: channel.id }); + await Channel.deleteChannel(channel); + } - //leave all group channels - const groupChannels = await Channel.find({ - where: { type: ChannelType.GROUP_DM }, - relations: ["recipients"], - select: { - id: true, - owner_id: true, - recipients: { - id: true, - user_id: true, - }, - }, - }); + //leave all group channels + const groupChannels = await Channel.find({ + where: { type: ChannelType.GROUP_DM }, + relations: ["recipients"], + select: { + id: true, + owner_id: true, + recipients: { + id: true, + user_id: true, + }, + }, + }); - await Promise.all( - groupChannels.map(async (channel) => { - const recipient = channel.recipients!.find((r) => r.user_id === user.id); - if (recipient) { - await Recipient.delete({ id: recipient.id }); - await emitEvent({ - event: "CHANNEL_RECIPIENT_REMOVE", - data: { - user: user.toPublicUser(), - channel_id: channel.id, - }, - channel_id: channel.id, - } as ChannelRecipientRemoveEvent); - console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`); - } + await Promise.all( + groupChannels.map(async (channel) => { + const recipient = channel.recipients!.find((r) => r.user_id === user.id); + if (recipient) { + await Recipient.delete({ id: recipient.id }); + await emitEvent({ + event: "CHANNEL_RECIPIENT_REMOVE", + data: { + user: user.toPublicUser(), + channel_id: channel.id, + }, + channel_id: channel.id, + } as ChannelRecipientRemoveEvent); + console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`); + } - // if no recipients remain, delete the channel - const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } }); - if (remainingRecipients.length === 0) { - await emitEvent({ - event: "CHANNEL_DELETE", - data: channel.toJSON(), - channel_id: channel.id, - } as ChannelDeleteEvent); - await Channel.deleteChannel(channel); - console.log(`[Instance ban] Deleted empty group channel ${channel.id}`); - } else { - // otherwise, if the banned user was the owner, reassign ownership - if (channel.owner_id === user.id) { - channel.owner_id = remainingRecipients[0].user_id; - await channel.save(); - console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`); - } - } - }), - ); + // if no recipients remain, delete the channel + const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } }); + if (remainingRecipients.length === 0) { + await emitEvent({ + event: "CHANNEL_DELETE", + data: channel.toJSON(), + channel_id: channel.id, + } as ChannelDeleteEvent); + await Channel.deleteChannel(channel); + console.log(`[Instance ban] Deleted empty group channel ${channel.id}`); + } else { + // otherwise, if the banned user was the owner, reassign ownership + if (channel.owner_id === user.id) { + channel.owner_id = remainingRecipients[0].user_id; + await channel.save(); + console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`); + } + } + }), + ); - // change ownership on guilds - const guilds = await Guild.find({ where: { owner_id: req.params.user_id } }); - await Promise.all( - guilds.map(async (guild) => { - const members = await Member.find({ - where: { guild_id: guild.id, id: Not(req.params.user_id) }, - relations: { roles: true }, - select: { id: true, roles: { id: true, position: true } }, - }); - const sortedMembers = members - .filter((m) => m.id !== req.params.user_id) - .sort((a, b) => { - const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number }); - const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number }); - return bHighestRole.position - aHighestRole.position; - }); - if (sortedMembers.length === 0) { - // no members left, delete guild - await guild.remove(); - console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`); - } else { - // assign new owner - guild.owner_id = sortedMembers[0].id; - await guild.save(); - console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`); + // change ownership on guilds + const guilds = await Guild.find({ where: { owner_id: req.params.user_id } }); + await Promise.all( + guilds.map(async (guild) => { + const members = await Member.find({ + where: { guild_id: guild.id, id: Not(req.params.user_id) }, + relations: { roles: true }, + select: { id: true, roles: { id: true, position: true } }, + }); + const sortedMembers = members + .filter((m) => m.id !== req.params.user_id) + .sort((a, b) => { + const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number }); + const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number }); + return bHighestRole.position - aHighestRole.position; + }); + if (sortedMembers.length === 0) { + // no members left, delete guild + await guild.remove(); + console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`); + } else { + // assign new owner + guild.owner_id = sortedMembers[0].id; + await guild.save(); + console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`); - // safety - reassign emojis/stickers owned by the old owner - const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } }); - await Promise.all( - stickers.map(async (sticker) => { - sticker.user_id = guild.owner_id; - await sticker.save(); - console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`); - }), - ); + // safety - reassign emojis/stickers owned by the old owner + const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } }); + await Promise.all( + stickers.map(async (sticker) => { + sticker.user_id = guild.owner_id; + await sticker.save(); + console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`); + }), + ); - const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } }); - await Promise.all( - emojis.map(async (emoji) => { - emoji.user_id = guild.owner_id!; - await emoji.save(); - console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`); - }), - ); - } - }), - ); + const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } }); + await Promise.all( + emojis.map(async (emoji) => { + emoji.user_id = guild.owner_id!; + await emoji.save(); + console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`); + }), + ); + } + }), + ); - const members = await Member.find({ where: { id: req.params.user_id } }); - await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); - await UserSettingsProtos.delete({ user_id: req.params.user_id }); - await User.delete({ id: req.params.user_id }); + const members = await Member.find({ where: { id: req.params.user_id } }); + await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); + await UserSettingsProtos.delete({ user_id: req.params.user_id }); + await User.delete({ id: req.params.user_id }); - // TODO: respect intents as USER_DELETE has potential to cause privacy issues - await emitEvent({ - event: "USER_DELETE", - user_id: req.user_id, - data: { user_id: req.params.user_id }, - } as UserDeleteEvent); + // TODO: respect intents as USER_DELETE has potential to cause privacy issues + await emitEvent({ + event: "USER_DELETE", + user_id: req.user_id, + data: { user_id: req.params.user_id }, + } as UserDeleteEvent); - console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`); - res.sendStatus(204); - }, + console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`); + res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/users/\043user_id/index.ts" "b/src/api/routes/users/\043user_id/index.ts" index 417f961..7fca1e0 100644 --- "a/src/api/routes/users/\043user_id/index.ts" +++ "b/src/api/routes/users/\043user_id/index.ts" @@ -23,19 +23,19 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIPublicUser", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "APIPublicUser", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req.params; - res.json(await User.getPublicUser(user_id)); - }, + res.json(await User.getPublicUser(user_id)); + }, ); export default router; diff --git "a/src/api/routes/users/\043user_id/messages.ts" "b/src/api/routes/users/\043user_id/messages.ts" index 717bca9..95c7cfc 100644 --- "a/src/api/routes/users/\043user_id/messages.ts" +++ "b/src/api/routes/users/\043user_id/messages.ts" @@ -23,33 +23,33 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "DmMessagesResponseSchema", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ where: { id: req.params.user_id } }); - const channel = await user.getDmChannelWith(req.user_id); + "/", + route({ + responses: { + 200: { + body: "DmMessagesResponseSchema", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ where: { id: req.params.user_id } }); + const channel = await user.getDmChannelWith(req.user_id); - const messages = ( - await Message.find({ - where: { channel_id: channel?.id }, - order: { timestamp: "DESC" }, - take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount), - }) - ).filter((x) => x !== null) as Message[]; + const messages = ( + await Message.find({ + where: { channel_id: channel?.id }, + order: { timestamp: "DESC" }, + take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount), + }) + ).filter((x) => x !== null) as Message[]; - const filteredMessages = messages.map((message) => message.toPartialMessage()) as DmMessagesResponseSchema; + const filteredMessages = messages.map((message) => message.toPartialMessage()) as DmMessagesResponseSchema; - return res.status(200).send(filteredMessages); - }, + return res.status(200).send(filteredMessages); + }, ); // TODO: POST to send a message to the user diff --git "a/src/api/routes/users/\043user_id/profile.ts" "b/src/api/routes/users/\043user_id/profile.ts" index a5ea3e9..bcd59cd 100644 --- "a/src/api/routes/users/\043user_id/profile.ts" +++ "b/src/api/routes/users/\043user_id/profile.ts" @@ -25,146 +25,146 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({ responses: { 200: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => { - if (req.params.user_id === "@me") req.params.user_id = req.user_id; + if (req.params.user_id === "@me") req.params.user_id = req.user_id; - const { guild_id, with_mutual_guilds, with_mutual_friends, with_mutual_friends_count } = req.query; + const { guild_id, with_mutual_guilds, with_mutual_friends, with_mutual_friends_count } = req.query; - const user = await User.getPublicUser(req.params.user_id, { - relations: ["connected_accounts"], - }); + const user = await User.getPublicUser(req.params.user_id, { + relations: ["connected_accounts"], + }); - const mutual_guilds: object[] = []; - let premium_guild_since; + const mutual_guilds: object[] = []; + let premium_guild_since; - if (with_mutual_guilds == "true") { - const requested_member = await Member.find({ - where: { id: req.params.user_id }, - }); - const self_member = await Member.find({ - where: { id: req.user_id }, - }); + if (with_mutual_guilds == "true") { + const requested_member = await Member.find({ + where: { id: req.params.user_id }, + }); + const self_member = await Member.find({ + where: { id: req.user_id }, + }); - for (const rmem of requested_member) { - if (rmem.premium_since) { - if (premium_guild_since) { - if (premium_guild_since > rmem.premium_since) { - premium_guild_since = rmem.premium_since; - } - } else { - premium_guild_since = rmem.premium_since; - } - } - for (const smem of self_member) { - if (smem.guild_id === rmem.guild_id) { - mutual_guilds.push({ - id: rmem.guild_id, - nick: rmem.nick, - }); - } - } - } - } + for (const rmem of requested_member) { + if (rmem.premium_since) { + if (premium_guild_since) { + if (premium_guild_since > rmem.premium_since) { + premium_guild_since = rmem.premium_since; + } + } else { + premium_guild_since = rmem.premium_since; + } + } + for (const smem of self_member) { + if (smem.guild_id === rmem.guild_id) { + mutual_guilds.push({ + id: rmem.guild_id, + nick: rmem.nick, + }); + } + } + } + } - const guild_member = - guild_id && typeof guild_id == "string" - ? await Member.findOneOrFail({ - where: { id: req.params.user_id, guild_id: guild_id }, - relations: ["roles"], - }) - : undefined; + const guild_member = + guild_id && typeof guild_id == "string" + ? await Member.findOneOrFail({ + where: { id: req.params.user_id, guild_id: guild_id }, + relations: ["roles"], + }) + : undefined; - // TODO: make proper DTO's in util? + // TODO: make proper DTO's in util? - const userProfile = { - bio: req.user_bot ? null : user.bio, - accent_color: user.accent_color, - banner: user.banner, - pronouns: user.pronouns, - theme_colors: user.theme_colors?.map((t) => Number(t)), // these are strings for some reason, they should be numbers - }; + const userProfile = { + bio: req.user_bot ? null : user.bio, + accent_color: user.accent_color, + banner: user.banner, + pronouns: user.pronouns, + theme_colors: user.theme_colors?.map((t) => Number(t)), // these are strings for some reason, they should be numbers + }; - const guildMemberProfile = { - accent_color: null, - banner: guild_member?.banner || null, - bio: guild_member?.bio || "", - guild_id, - }; + const guildMemberProfile = { + accent_color: null, + banner: guild_member?.banner || null, + bio: guild_member?.bio || "", + guild_id, + }; - const badges = await Badge.find(); + const badges = await Badge.find(); - let mutual_friends: PublicUser[] = []; - let mutual_friends_count = 0; + let mutual_friends: PublicUser[] = []; + let mutual_friends_count = 0; - if (with_mutual_friends == "true" || with_mutual_friends_count == "true") { - const relationshipsSelf = await Relationship.find({ where: { from_id: req.user_id, type: RelationshipType.friends } }); - const relationshipsUser = await Relationship.find({ where: { from_id: req.params.user_id, type: RelationshipType.friends } }); - const relationshipsIntersection = relationshipsSelf.filter((r1) => relationshipsUser.some((r2) => r2.to_id === r1.to_id)); - if (with_mutual_friends_count) mutual_friends_count = relationshipsIntersection.length; - if (with_mutual_friends) { - const users = await User.find({ where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, select: PublicUserProjection }); - mutual_friends = users.map((u) => u.toPublicUser()); - } - } + if (with_mutual_friends == "true" || with_mutual_friends_count == "true") { + const relationshipsSelf = await Relationship.find({ where: { from_id: req.user_id, type: RelationshipType.friends } }); + const relationshipsUser = await Relationship.find({ where: { from_id: req.params.user_id, type: RelationshipType.friends } }); + const relationshipsIntersection = relationshipsSelf.filter((r1) => relationshipsUser.some((r2) => r2.to_id === r1.to_id)); + if (with_mutual_friends_count) mutual_friends_count = relationshipsIntersection.length; + if (with_mutual_friends) { + const users = await User.find({ where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, select: PublicUserProjection }); + mutual_friends = users.map((u) => u.toPublicUser()); + } + } - res.json({ - connected_accounts: user.connected_accounts.filter((x) => x.visibility != 0), - premium_guild_since: premium_guild_since, // TODO - premium_since: user.premium_since, // TODO - mutual_guilds: with_mutual_guilds ? mutual_guilds : undefined, // TODO {id: "", nick: null} when ?with_mutual_guilds=true - mutual_friends: with_mutual_friends ? mutual_friends : undefined, - mutual_friends_count: with_mutual_friends_count ? mutual_friends_count : undefined, - user: user.toPublicUser(), - premium_type: user.premium_type, - profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason? - user_profile: userProfile, - guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() }, - guild_member_profile: guild_id && guildMemberProfile, - badges: badges.filter((x) => user.badge_ids?.includes(x.id)), - }); + res.json({ + connected_accounts: user.connected_accounts.filter((x) => x.visibility != 0), + premium_guild_since: premium_guild_since, // TODO + premium_since: user.premium_since, // TODO + mutual_guilds: with_mutual_guilds ? mutual_guilds : undefined, // TODO {id: "", nick: null} when ?with_mutual_guilds=true + mutual_friends: with_mutual_friends ? mutual_friends : undefined, + mutual_friends_count: with_mutual_friends_count ? mutual_friends_count : undefined, + user: user.toPublicUser(), + premium_type: user.premium_type, + profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason? + user_profile: userProfile, + guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() }, + guild_member_profile: guild_id && guildMemberProfile, + badges: badges.filter((x) => user.badge_ids?.includes(x.id)), + }); }); router.patch("/", route({ requestBody: "UserProfileModifySchema" }), async (req: Request, res: Response) => { - const body = req.body as UserProfileModifySchema; + const body = req.body as UserProfileModifySchema; - if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: [...PrivateUserProjection, "data"], - }); + if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: [...PrivateUserProjection, "data"], + }); - if (body.bio) { - const { maxBio } = Config.get().limits.user; - if (body.bio.length > maxBio) { - throw FieldErrors({ - bio: { - code: "BIO_INVALID", - message: `Bio must be less than ${maxBio} in length`, - }, - }); - } - } + if (body.bio) { + const { maxBio } = Config.get().limits.user; + if (body.bio.length > maxBio) { + throw FieldErrors({ + bio: { + code: "BIO_INVALID", + message: `Bio must be less than ${maxBio} in length`, + }, + }); + } + } - user.assign(body); - await user.save(); + user.assign(body); + await user.save(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - delete user.data; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete user.data; - // TODO: send update member list event in gateway - await emitEvent({ - event: "USER_UPDATE", - user_id: req.user_id, - data: user, - } as UserUpdateEvent); + // TODO: send update member list event in gateway + await emitEvent({ + event: "USER_UPDATE", + user_id: req.user_id, + data: user, + } as UserUpdateEvent); - res.json({ - accent_color: user.accent_color, - bio: user.bio, - banner: user.banner, - theme_colors: user.theme_colors, - pronouns: user.pronouns, - }); + res.json({ + accent_color: user.accent_color, + bio: user.bio, + banner: user.banner, + theme_colors: user.theme_colors, + pronouns: user.pronouns, + }); }); export default router; diff --git "a/src/api/routes/users/\043user_id/relationships.ts" "b/src/api/routes/users/\043user_id/relationships.ts" index 7e33291..3272ef7 100644 --- "a/src/api/routes/users/\043user_id/relationships.ts" +++ "b/src/api/routes/users/\043user_id/relationships.ts" @@ -24,44 +24,44 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { body: "UserRelationsResponse" }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const mutual_relations: UserRelationsResponse = []; + "/", + route({ + responses: { + 200: { body: "UserRelationsResponse" }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const mutual_relations: UserRelationsResponse = []; - const requested_relations = await User.findOneOrFail({ - where: { id: req.params.user_id }, - relations: ["relationships"], - }); - const self_relations = await User.findOneOrFail({ - where: { id: req.user_id }, - relations: ["relationships"], - }); + const requested_relations = await User.findOneOrFail({ + where: { id: req.params.user_id }, + relations: ["relationships"], + }); + const self_relations = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships"], + }); - for (const rmem of requested_relations.relationships) { - for (const smem of self_relations.relationships) - if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) { - const relation_user = await User.getPublicUser(rmem.to_id); + for (const rmem of requested_relations.relationships) { + for (const smem of self_relations.relationships) + if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) { + const relation_user = await User.getPublicUser(rmem.to_id); - mutual_relations.push({ - id: relation_user.id, - username: relation_user.username, - avatar: relation_user.avatar, - discriminator: relation_user.discriminator, - public_flags: relation_user.public_flags, - }); - } - } + mutual_relations.push({ + id: relation_user.id, + username: relation_user.username, + avatar: relation_user.avatar, + discriminator: relation_user.discriminator, + public_flags: relation_user.public_flags, + }); + } + } - res.json(mutual_relations); - }, + res.json(mutual_relations); + }, ); export default router; diff --git a/src/api/routes/users/@me/activities/statistics/applications.ts b/src/api/routes/users/@me/activities/statistics/applications.ts index a9bc596..b011916 100644 --- a/src/api/routes/users/@me/activities/statistics/applications.ts +++ b/src/api/routes/users/@me/activities/statistics/applications.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.json([]).status(200); + // TODO: + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/affinities/guilds.ts b/src/api/routes/users/@me/affinities/guilds.ts index 30fe887..8de9aa5 100644 --- a/src/api/routes/users/@me/affinities/guilds.ts +++ b/src/api/routes/users/@me/affinities/guilds.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.status(200).send({ guild_affinities: [] }); + // TODO: + res.status(200).send({ guild_affinities: [] }); }); export default router; diff --git a/src/api/routes/users/@me/affinities/users.ts b/src/api/routes/users/@me/affinities/users.ts index 038f410..2f66625 100644 --- a/src/api/routes/users/@me/affinities/users.ts +++ b/src/api/routes/users/@me/affinities/users.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); + // TODO: + res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); }); export default router; diff --git "a/src/api/routes/users/@me/applications/\043application_id/entitlements.ts" "b/src/api/routes/users/@me/applications/\043application_id/entitlements.ts" index f1f9a29..e8e6f65 100644 --- "a/src/api/routes/users/@me/applications/\043application_id/entitlements.ts" +++ "b/src/api/routes/users/@me/applications/\043application_id/entitlements.ts" @@ -22,8 +22,8 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([]).status(200); + //TODO + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/billing/country-code.ts b/src/api/routes/users/@me/billing/country-code.ts index 1907536..8bddafc 100644 --- a/src/api/routes/users/@me/billing/country-code.ts +++ b/src/api/routes/users/@me/billing/country-code.ts @@ -22,8 +22,8 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json({ country_code: "US" }).status(200); + //TODO + res.json({ country_code: "US" }).status(200); }); export default router; diff --git a/src/api/routes/users/@me/billing/location-info.ts b/src/api/routes/users/@me/billing/location-info.ts index 3fe7f67..918ea91 100644 --- a/src/api/routes/users/@me/billing/location-info.ts +++ b/src/api/routes/users/@me/billing/location-info.ts @@ -22,9 +22,9 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - // TODO: subdivision_code (optional) - res.json({ country_code: "US" }).status(200); + //TODO + // TODO: subdivision_code (optional) + res.json({ country_code: "US" }).status(200); }); export default router; diff --git a/src/api/routes/users/@me/billing/payment-sources.ts b/src/api/routes/users/@me/billing/payment-sources.ts index c2ebd3f..1380ed5 100644 --- a/src/api/routes/users/@me/billing/payment-sources.ts +++ b/src/api/routes/users/@me/billing/payment-sources.ts @@ -23,59 +23,59 @@ // https://docs.discord.food/resources/billing#example-payment-source const example = { - id: "1422548914485198869", - type: 1, - invalid: false, - flags: 2, - deleted_at: null, - brand: "visa", - last_4: "4242", - expires_month: 9, - expires_year: 2077, - billing_address: { - name: "John Doe", - line_1: "123 Main Street", - line_2: "Apt 4B", - city: "San Francisco", - state: "CA", - country: "US", - postal_code: "94105", - }, - country: "US", - payment_gateway: 1, - payment_gateway_source_id: "pm_DwiVlGlYwe1qxLzy4QWChQeo", - default: false, + id: "1422548914485198869", + type: 1, + invalid: false, + flags: 2, + deleted_at: null, + brand: "visa", + last_4: "4242", + expires_month: 9, + expires_year: 2077, + billing_address: { + name: "John Doe", + line_1: "123 Main Street", + line_2: "Apt 4B", + city: "San Francisco", + state: "CA", + country: "US", + postal_code: "94105", + }, + country: "US", + payment_gateway: 1, + payment_gateway_source_id: "pm_DwiVlGlYwe1qxLzy4QWChQeo", + default: false, }; router.get("/", route({}), (req: Request, res: Response) => { - // TODO: schema - res.json([example]).status(200); + // TODO: schema + res.json([example]).status(200); }); router.post("/", route({}), (req: Request, res: Response) => { - // TODO: schema - res.json([example]).status(200); + // TODO: schema + res.json([example]).status(200); }); router.get("/:payment_source_id", route({}), (req: Request, res: Response) => { - // TODO: schema - res.json({ - ...example, - id: req.route.payment_source_id, - }).status(200); + // TODO: schema + res.json({ + ...example, + id: req.route.payment_source_id, + }).status(200); }); router.patch("/:payment_source_id", route({}), (req: Request, res: Response) => { - // TODO: schema - res.json({ - ...example, - id: req.route.payment_source_id, - }).status(200); + // TODO: schema + res.json({ + ...example, + id: req.route.payment_source_id, + }).status(200); }); router.delete("/:payment_source_id", route({}), (req: Request, res: Response) => { - // TODO: schema - res.status(204); + // TODO: schema + res.status(204); }); export default router; diff --git a/src/api/routes/users/@me/billing/subscriptions.ts b/src/api/routes/users/@me/billing/subscriptions.ts index f1f9a29..e8e6f65 100644 --- a/src/api/routes/users/@me/billing/subscriptions.ts +++ b/src/api/routes/users/@me/billing/subscriptions.ts @@ -22,8 +22,8 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([]).status(200); + //TODO + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts index 9411e72..e13b874 100644 --- a/src/api/routes/users/@me/channels.ts +++ b/src/api/routes/users/@me/channels.ts @@ -24,37 +24,37 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIDMChannelArray", - }, - }, - }), - async (req: Request, res: Response) => { - const recipients = await Recipient.find({ - where: { user_id: req.user_id, closed: false }, - relations: ["channel", "channel.recipients"], - }); - res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])))); - }, + "/", + route({ + responses: { + 200: { + body: "APIDMChannelArray", + }, + }, + }), + async (req: Request, res: Response) => { + const recipients = await Recipient.find({ + where: { user_id: req.user_id, closed: false }, + relations: ["channel", "channel.recipients"], + }); + res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])))); + }, ); router.post( - "/", - route({ - requestBody: "DmChannelCreateSchema", - responses: { - 200: { - body: "DmChannelDTO", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as DmChannelCreateSchema; - res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name)); - }, + "/", + route({ + requestBody: "DmChannelCreateSchema", + responses: { + 200: { + body: "DmChannelDTO", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as DmChannelCreateSchema; + res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name)); + }, ); export default router; diff --git a/src/api/routes/users/@me/collectibles-marketing.ts b/src/api/routes/users/@me/collectibles-marketing.ts index c9f1a1d..a043a14 100644 --- a/src/api/routes/users/@me/collectibles-marketing.ts +++ b/src/api/routes/users/@me/collectibles-marketing.ts @@ -24,26 +24,26 @@ // Unsure what this endpoint does, it seems to only affect the visual style of the shop tab in home router.get( - "/", - route({ - responses: { - 200: { - body: "CollectiblesMarketingResponse", - }, - 204: {}, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - res.send({ - marketings: {}, - } as CollectiblesMarketingResponse); - }, + "/", + route({ + responses: { + 200: { + body: "CollectiblesMarketingResponse", + }, + 204: {}, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + res.send({ + marketings: {}, + } as CollectiblesMarketingResponse); + }, ); export default router; diff --git a/src/api/routes/users/@me/collectibles-purchases.ts b/src/api/routes/users/@me/collectibles-purchases.ts index 9491e30..1e6a08d 100644 --- a/src/api/routes/users/@me/collectibles-purchases.ts +++ b/src/api/routes/users/@me/collectibles-purchases.ts @@ -23,24 +23,24 @@ // Unsure what this endpoint does, it seems to only affect the visual style of the shop tab in home router.get( - "/", - route({ - responses: { - 200: { - // body: "CollectiblesPurchasesResponse", - }, - 204: {}, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - res.send([]); - }, + "/", + route({ + responses: { + 200: { + // body: "CollectiblesPurchasesResponse", + }, + 204: {}, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + res.send([]); + }, ); export default router; diff --git "a/src/api/routes/users/@me/connections/\043connection_name/\043connection_id/access-token.ts" "b/src/api/routes/users/@me/connections/\043connection_name/\043connection_id/access-token.ts" index fcab432..8846efe 100644 --- "a/src/api/routes/users/@me/connections/\043connection_name/\043connection_id/access-token.ts" +++ "b/src/api/routes/users/@me/connections/\043connection_name/\043connection_id/access-token.ts" @@ -28,49 +28,49 @@ // NOTE: this route has not been extensively tested, as the required connections are not implemented as of writing router.get("/", route({}), async (req: Request, res: Response) => { - const { connection_name, connection_id } = req.params; + const { connection_name, connection_id } = req.params; - const connection = ConnectionStore.connections.get(connection_name); + const connection = ConnectionStore.connections.get(connection_name); - if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection) - throw FieldErrors({ - provider_id: { - code: "BASE_TYPE_CHOICES", - message: req.t("common:field.BASE_TYPE_CHOICES", { - types: ALLOWED_CONNECTIONS.join(", "), - }), - }, - }); + if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection) + throw FieldErrors({ + provider_id: { + code: "BASE_TYPE_CHOICES", + message: req.t("common:field.BASE_TYPE_CHOICES", { + types: ALLOWED_CONNECTIONS.join(", "), + }), + }, + }); - if (!connection.settings.enabled) - throw FieldErrors({ - provider_id: { - message: "This connection has been disabled server-side.", - }, - }); + if (!connection.settings.enabled) + throw FieldErrors({ + provider_id: { + message: "This connection has been disabled server-side.", + }, + }); - const connectedAccount = await ConnectedAccount.findOne({ - where: { - type: connection_name, - external_id: connection_id, - user_id: req.user_id, - }, - select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"], - }); - if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION; - if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED; - if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400); + const connectedAccount = await ConnectedAccount.findOne({ + where: { + type: connection_name, + external_id: connection_id, + user_id: req.user_id, + }, + select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"], + }); + if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION; + if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED; + if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400); - let access_token = connectedAccount.token_data.access_token; - const { expires_at, expires_in, fetched_at } = connectedAccount.token_data; + let access_token = connectedAccount.token_data.access_token; + const { expires_at, expires_in, fetched_at } = connectedAccount.token_data; - if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) { - if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400); - const tokenData = await connection.refresh(connectedAccount); - access_token = tokenData.access_token; - } + if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) { + if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400); + const tokenData = await connection.refresh(connectedAccount); + access_token = tokenData.access_token; + } - res.json({ access_token }); + res.json({ access_token }); }); export default router; diff --git "a/src/api/routes/users/@me/connections/\043connection_name/\043connection_id/index.ts" "b/src/api/routes/users/@me/connections/\043connection_name/\043connection_id/index.ts" index 37deee3..111312a 100644 --- "a/src/api/routes/users/@me/connections/\043connection_name/\043connection_id/index.ts" +++ "b/src/api/routes/users/@me/connections/\043connection_name/\043connection_id/index.ts" @@ -24,65 +24,65 @@ // TODO: connection update schema router.patch("/", route({ requestBody: "ConnectionUpdateSchema" }), async (req: Request, res: Response) => { - const { connection_name, connection_id } = req.params; - const body = req.body as ConnectionUpdateSchema; + const { connection_name, connection_id } = req.params; + const body = req.body as ConnectionUpdateSchema; - const connection = await ConnectedAccount.findOne({ - where: { - user_id: req.user_id, - external_id: connection_id, - type: connection_name, - }, - select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"], - }); + const connection = await ConnectedAccount.findOne({ + where: { + user_id: req.user_id, + external_id: connection_id, + type: connection_name, + }, + select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"], + }); - if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION; - // TODO: do we need to do anything if the connection is revoked? + if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION; + // TODO: do we need to do anything if the connection is revoked? - if (typeof body.visibility === "boolean") - //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? - body.visibility = body.visibility ? 1 : 0; - if (typeof body.show_activity === "boolean") - //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? - body.show_activity = body.show_activity ? 1 : 0; - if (typeof body.metadata_visibility === "boolean") - //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? - body.metadata_visibility = body.metadata_visibility ? 1 : 0; + if (typeof body.visibility === "boolean") + //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? + body.visibility = body.visibility ? 1 : 0; + if (typeof body.show_activity === "boolean") + //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? + body.show_activity = body.show_activity ? 1 : 0; + if (typeof body.metadata_visibility === "boolean") + //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? + body.metadata_visibility = body.metadata_visibility ? 1 : 0; - connection.assign(req.body); + connection.assign(req.body); - await ConnectedAccount.update( - { - user_id: req.user_id, - external_id: connection_id, - type: connection_name, - }, - connection, - ); - res.json(connection.toJSON()); + await ConnectedAccount.update( + { + user_id: req.user_id, + external_id: connection_id, + type: connection_name, + }, + connection, + ); + res.json(connection.toJSON()); }); router.delete("/", route({}), async (req: Request, res: Response) => { - const { connection_name, connection_id } = req.params; + const { connection_name, connection_id } = req.params; - const account = await ConnectedAccount.findOneOrFail({ - where: { - user_id: req.user_id, - external_id: connection_id, - type: connection_name, - }, - }); + const account = await ConnectedAccount.findOneOrFail({ + where: { + user_id: req.user_id, + external_id: connection_id, + type: connection_name, + }, + }); - await Promise.all([ - ConnectedAccount.remove(account), - emitEvent({ - event: "USER_CONNECTIONS_UPDATE", - data: account, - user_id: req.user_id, - }), - ]); + await Promise.all([ + ConnectedAccount.remove(account), + emitEvent({ + event: "USER_CONNECTIONS_UPDATE", + data: account, + user_id: req.user_id, + }), + ]); - return res.sendStatus(200); + return res.sendStatus(200); }); export default router; diff --git a/src/api/routes/users/@me/connections/index.ts b/src/api/routes/users/@me/connections/index.ts index 00625ee..a2d8b82 100644 --- a/src/api/routes/users/@me/connections/index.ts +++ b/src/api/routes/users/@me/connections/index.ts @@ -23,14 +23,14 @@ const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const connections = await ConnectedAccount.find({ - where: { - user_id: req.user_id, - }, - select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"], - }); + const connections = await ConnectedAccount.find({ + where: { + user_id: req.user_id, + }, + select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"], + }); - res.json(connections.map((x) => new ConnectedAccountDTO(x, true))); + res.json(connections.map((x) => new ConnectedAccountDTO(x, true))); }); export default router; diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts index 4386118..668f7c1 100644 --- a/src/api/routes/users/@me/delete.ts +++ b/src/api/routes/users/@me/delete.ts @@ -25,44 +25,44 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - responses: { - 204: {}, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); //User object - let correctpass = true; + "/", + route({ + responses: { + 204: {}, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); //User object + let correctpass = true; - if (user.data.hash) { - // guest accounts can delete accounts without password - correctpass = await bcrypt.compare(req.body.password, user.data.hash); - if (!correctpass) { - throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); - } - } + if (user.data.hash) { + // guest accounts can delete accounts without password + correctpass = await bcrypt.compare(req.body.password, user.data.hash); + if (!correctpass) { + throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); + } + } - // TODO: decrement guild member count + // TODO: decrement guild member count - if (correctpass) { - const members = await Member.find({ where: { id: req.user_id } }); - await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); + if (correctpass) { + const members = await Member.find({ where: { id: req.user_id } }); + await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); - res.sendStatus(204); - } else { - res.sendStatus(401); - } - }, + res.sendStatus(204); + } else { + res.sendStatus(401); + } + }, ); export default router; diff --git a/src/api/routes/users/@me/devices.ts b/src/api/routes/users/@me/devices.ts index 644302c..d320613 100644 --- a/src/api/routes/users/@me/devices.ts +++ b/src/api/routes/users/@me/devices.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.post("/", route({}), (req: Request, res: Response) => { - // TODO: - res.sendStatus(204); + // TODO: + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts index 73de839..95995e4 100644 --- a/src/api/routes/users/@me/disable.ts +++ b/src/api/routes/users/@me/disable.ts @@ -24,41 +24,41 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); //User object - let correctpass = true; + "/", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); //User object + let correctpass = true; - if (user.data.hash) { - // guest accounts can delete accounts without password - correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/ - } + if (user.data.hash) { + // guest accounts can delete accounts without password + correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/ + } - if (correctpass) { - await User.update({ id: req.user_id }, { disabled: true }); + if (correctpass) { + await User.update({ id: req.user_id }, { disabled: true }); - res.sendStatus(204); - } else { - res.status(400).json({ - message: "Password does not match", - code: 50018, - }); - } - }, + res.sendStatus(204); + } else { + res.status(400).json({ + message: "Password does not match", + code: 50018, + }); + } + }, ); export default router; diff --git a/src/api/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts index c557500..ede7353 100644 --- a/src/api/routes/users/@me/email-settings.ts +++ b/src/api/routes/users/@me/email-settings.ts @@ -22,17 +22,17 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.json({ - categories: { - social: true, - communication: true, - tips: false, - updates_and_announcements: false, - recommendations_and_events: false, - }, - initialized: false, - }).status(200); + // TODO: + res.json({ + categories: { + social: true, + communication: true, + tips: false, + updates_and_announcements: false, + recommendations_and_events: false, + }, + initialized: false, + }).status(200); }); export default router; diff --git a/src/api/routes/users/@me/entitlements.ts b/src/api/routes/users/@me/entitlements.ts index fd953ee..ac73a12 100644 --- a/src/api/routes/users/@me/entitlements.ts +++ b/src/api/routes/users/@me/entitlements.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.get("/gifts", route({}), (req: Request, res: Response) => { - // TODO: - res.json([]).status(200); + // TODO: + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts index df71f46..c06c39c 100644 --- a/src/api/routes/users/@me/guilds.ts +++ b/src/api/routes/users/@me/guilds.ts @@ -24,62 +24,62 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIGuildArray", - }, - }, - }), - async (req: Request, res: Response) => { - const members = await Member.find({ - relations: ["guild"], - where: { id: req.user_id }, - }); + "/", + route({ + responses: { + 200: { + body: "APIGuildArray", + }, + }, + }), + async (req: Request, res: Response) => { + const members = await Member.find({ + relations: ["guild"], + where: { id: req.user_id }, + }); - let guild = members.map((x) => x.guild); + let guild = members.map((x) => x.guild); - if ("with_counts" in req.query && req.query.with_counts == "true") { - guild = []; // TODO: Load guilds with user role permissions number - } + if ("with_counts" in req.query && req.query.with_counts == "true") { + guild = []; // TODO: Load guilds with user role permissions number + } - res.json(guild); - }, + res.json(guild); + }, ); // user send to leave a certain guild router.delete( - "/:guild_id", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { autoJoin } = Config.get().guild; - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: ["owner_id"], - }); + "/:guild_id", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { autoJoin } = Config.get().guild; + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: ["owner_id"], + }); - if (!guild) throw new HTTPError("Guild doesn't exist", 404); - if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400); - if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) { - throw new HTTPError("You can't leave instance auto join guilds", 400); - } + if (!guild) throw new HTTPError("Guild doesn't exist", 404); + if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400); + if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) { + throw new HTTPError("You can't leave instance auto join guilds", 400); + } - await Member.removeFromGuild(req.user_id, guild_id); + await Member.removeFromGuild(req.user_id, guild_id); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git "a/src/api/routes/users/@me/guilds/\043guild_id/settings.ts" "b/src/api/routes/users/@me/guilds/\043guild_id/settings.ts" index 7fda9a9..16b64ec 100644 --- "a/src/api/routes/users/@me/guilds/\043guild_id/settings.ts" +++ "b/src/api/routes/users/@me/guilds/\043guild_id/settings.ts" @@ -25,54 +25,54 @@ // GET doesn't exist on discord.com router.get( - "/", - route({ - responses: { - 200: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const user = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id: req.params.guild_id }, - select: ["settings"], - }); - return res.json(user.settings); - }, + "/", + route({ + responses: { + 200: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const user = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id: req.params.guild_id }, + select: ["settings"], + }); + return res.json(user.settings); + }, ); router.patch( - "/", - route({ - requestBody: "UserGuildSettingsSchema", - responses: { - 200: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as UserGuildSettingsSchema; + "/", + route({ + requestBody: "UserGuildSettingsSchema", + responses: { + 200: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as UserGuildSettingsSchema; - if (body.channel_overrides) { - for (const channel in body.channel_overrides) { - Channel.findOneOrFail({ where: { id: channel } }); - } - } + if (body.channel_overrides) { + for (const channel in body.channel_overrides) { + Channel.findOneOrFail({ where: { id: channel } }); + } + } - const user = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id: req.params.guild_id }, - select: ["settings"], - }); - OrmUtils.mergeDeep(user.settings || {}, body); - Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user); + const user = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id: req.params.guild_id }, + select: ["settings"], + }); + OrmUtils.mergeDeep(user.settings || {}, body); + Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user); - res.json(user.settings); - }, + res.json(user.settings); + }, ); export default router; diff --git a/src/api/routes/users/@me/guilds/premium/subscription-slots.ts b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts index a9bc596..b011916 100644 --- a/src/api/routes/users/@me/guilds/premium/subscription-slots.ts +++ b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.json([]).status(200); + // TODO: + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts index aba9f92..bd1fa07 100644 --- a/src/api/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts @@ -25,182 +25,182 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIPrivateUser", - }, - }, - }), - async (req: Request, res: Response) => { - res.json( - await User.findOne({ - select: PrivateUserProjection, - where: { id: req.user_id }, - }), - ); - }, + "/", + route({ + responses: { + 200: { + body: "APIPrivateUser", + }, + }, + }), + async (req: Request, res: Response) => { + res.json( + await User.findOne({ + select: PrivateUserProjection, + where: { id: req.user_id }, + }), + ); + }, ); router.patch( - "/", - route({ - requestBody: "UserModifySchema", - responses: { - 200: { - body: "UserUpdateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as UserModifySchema; + "/", + route({ + requestBody: "UserModifySchema", + responses: { + 200: { + body: "UserUpdateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as UserModifySchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: [...PrivateUserProjection, "data"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: [...PrivateUserProjection, "data"], + }); - // Populated on password change - let newToken: string | undefined; + // Populated on password change + let newToken: string | undefined; - if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); - if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); + if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); + if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - if (body.password) { - if (user.data?.hash) { - const same_password = await bcrypt.compare(body.password, user.data.hash || ""); - if (!same_password) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } - } else { - user.data.hash = await bcrypt.hash(body.password, 12); - } - } + if (body.password) { + if (user.data?.hash) { + const same_password = await bcrypt.compare(body.password, user.data.hash || ""); + if (!same_password) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } + } else { + user.data.hash = await bcrypt.hash(body.password, 12); + } + } - if (body.email) { - if (!body.email && Config.get().register.email.required) - throw FieldErrors({ - email: { - message: req.t("auth:register.EMAIL_INVALID"), - code: "EMAIL_INVALID", - }, - }); - if (!body.password) - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } + if (body.email) { + if (!body.email && Config.get().register.email.required) + throw FieldErrors({ + email: { + message: req.t("auth:register.EMAIL_INVALID"), + code: "EMAIL_INVALID", + }, + }); + if (!body.password) + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } - if (body.new_password) { - if (!body.password && user.email) { - throw FieldErrors({ - password: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } - user.data.hash = await bcrypt.hash(body.new_password, 12); - user.data.valid_tokens_since = new Date(); - newToken = (await generateToken(user.id)) as string; - } + if (body.new_password) { + if (!body.password && user.email) { + throw FieldErrors({ + password: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } + user.data.hash = await bcrypt.hash(body.new_password, 12); + user.data.valid_tokens_since = new Date(); + newToken = (await generateToken(user.id)) as string; + } - if (body.username) { - const check_username = body?.username?.replace(/\s/g, "").trim(); - if (!check_username) { - throw FieldErrors({ - username: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + if (body.username) { + const check_username = body?.username?.replace(/\s/g, "").trim(); + if (!check_username) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - const { maxUsername } = Config.get().limits.user; - if (check_username.length > maxUsername || check_username.length < 2) { - throw FieldErrors({ - username: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 2 and ${maxUsername} in length.`, - }, - }); - } + const { maxUsername } = Config.get().limits.user; + if (check_username.length > maxUsername || check_username.length < 2) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 2 and ${maxUsername} in length.`, + }, + }); + } - if (!body.password) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } - } + if (!body.password) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } + } - if (body.discriminator) { - if ( - await User.findOne({ - where: { - discriminator: body.discriminator, - username: body.username || user.username, - }, - }) - ) { - throw FieldErrors({ - discriminator: { - code: "INVALID_DISCRIMINATOR", - message: "This discriminator is already in use.", - }, - }); - } - } + if (body.discriminator) { + if ( + await User.findOne({ + where: { + discriminator: body.discriminator, + username: body.username || user.username, + }, + }) + ) { + throw FieldErrors({ + discriminator: { + code: "INVALID_DISCRIMINATOR", + message: "This discriminator is already in use.", + }, + }); + } + } - if (body.bio) { - const { maxBio } = Config.get().limits.user; - if (body.bio.length > maxBio) { - throw FieldErrors({ - bio: { - code: "BIO_INVALID", - message: `Bio must be less than ${maxBio} in length`, - }, - }); - } - } + if (body.bio) { + const { maxBio } = Config.get().limits.user; + if (body.bio.length > maxBio) { + throw FieldErrors({ + bio: { + code: "BIO_INVALID", + message: `Bio must be less than ${maxBio} in length`, + }, + }); + } + } - user.assign(body); - user.validate(); - await user.save(); + user.assign(body); + user.validate(); + await user.save(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - delete user.data; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + delete user.data; - // TODO: send update member list event in gateway - await emitEvent({ - event: "USER_UPDATE", - user_id: req.user_id, - data: user, - } as UserUpdateEvent); + // TODO: send update member list event in gateway + await emitEvent({ + event: "USER_UPDATE", + user_id: req.user_id, + data: user, + } as UserUpdateEvent); - res.json({ - ...user, - newToken, - }); - }, + res.json({ + ...user, + newToken, + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/library.ts b/src/api/routes/users/@me/library.ts index 845cd17..8d09b38 100644 --- a/src/api/routes/users/@me/library.ts +++ b/src/api/routes/users/@me/library.ts @@ -22,8 +22,8 @@ const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.status(200).send([]); + // TODO: + res.status(200).send([]); }); export default router; diff --git a/src/api/routes/users/@me/mentions.ts b/src/api/routes/users/@me/mentions.ts index 37a7abe..2d631e9 100644 --- a/src/api/routes/users/@me/mentions.ts +++ b/src/api/routes/users/@me/mentions.ts @@ -24,139 +24,139 @@ const router: Router = Router({ mergeParams: true }); router.get( - "", - route({ - responses: { - 200: { - body: "MessageListResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - // AFAICT this endpoint doesn't list DMs - async (req: Request, res: Response) => { - const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 25; - const everyone = req.query.everyone !== undefined ? Boolean(req.query.everyone) : true; - const roles = req.query.roles !== undefined ? Boolean(req.query.roles) : true; - const before = req.query.before !== undefined ? String(req.query.before as string) : undefined; - const guild_id = req.query.guild_id !== undefined ? req.query.guild_id : undefined; + "", + route({ + responses: { + 200: { + body: "MessageListResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + // AFAICT this endpoint doesn't list DMs + async (req: Request, res: Response) => { + const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 25; + const everyone = req.query.everyone !== undefined ? Boolean(req.query.everyone) : true; + const roles = req.query.roles !== undefined ? Boolean(req.query.roles) : true; + const before = req.query.before !== undefined ? String(req.query.before as string) : undefined; + const guild_id = req.query.guild_id !== undefined ? req.query.guild_id : undefined; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + }); - const memberships = await Member.find({ - where: { id: req.user_id, ...(guild_id === undefined ? {} : { guild_id: String(guild_id) }) }, - select: { - guild_id: true, - id: true, - communication_disabled_until: true, - roles: { - // We don't want to include all guild roles, as this could cause a lot more explosive behavior - id: true, - position: true, - permissions: true, - mentionable: true, // cause we can skip querying for unmentionable roles - }, - guild: { - id: true, - owner_id: true, - }, - }, - relations: ["guild", "roles"], - }); + const memberships = await Member.find({ + where: { id: req.user_id, ...(guild_id === undefined ? {} : { guild_id: String(guild_id) }) }, + select: { + guild_id: true, + id: true, + communication_disabled_until: true, + roles: { + // We don't want to include all guild roles, as this could cause a lot more explosive behavior + id: true, + position: true, + permissions: true, + mentionable: true, // cause we can skip querying for unmentionable roles + }, + guild: { + id: true, + owner_id: true, + }, + }, + relations: ["guild", "roles"], + }); - const channels = await Channel.find({ - where: { - guild_id: In(memberships.map((m) => m.guild_id)), - }, - select: { id: true, guild_id: true, permission_overwrites: true }, - }); + const channels = await Channel.find({ + where: { + guild_id: In(memberships.map((m) => m.guild_id)), + }, + select: { id: true, guild_id: true, permission_overwrites: true }, + }); - const visibleChannels = channels.filter((c) => { - const member = memberships.find((m) => m.guild_id === c.guild_id)!; - return Permissions.finalPermission({ - user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 }, - guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles }, - channel: c, - }).has("VIEW_CHANNEL"); - }); + const visibleChannels = channels.filter((c) => { + const member = memberships.find((m) => m.guild_id === c.guild_id)!; + return Permissions.finalPermission({ + user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 }, + guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles }, + channel: c, + }).has("VIEW_CHANNEL"); + }); - const visibleChannelIds = visibleChannels.map((c) => c.id); - const ownedMentionableRoleIds = memberships.reduce((acc, m) => { - acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id)); - return acc; - }, [] as Snowflake[]); + const visibleChannelIds = visibleChannels.map((c) => c.id); + const ownedMentionableRoleIds = memberships.reduce((acc, m) => { + acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id)); + return acc; + }, [] as Snowflake[]); - const whereQuery: FindOptionsWhere[] = [ - { - channel_id: In(visibleChannelIds), - mentions: { id: user.id }, - id: before ? LessThan(before) : undefined, - }, - ]; - if (everyone) { - whereQuery.push({ - channel_id: In(visibleChannelIds), - mention_everyone: true, - id: before ? LessThan(before) : undefined, - }); - } - if (roles) { - whereQuery.push({ - channel_id: In(visibleChannelIds), - mention_roles: { id: In(ownedMentionableRoleIds) }, - id: before ? LessThan(before) : undefined, - }); - } + const whereQuery: FindOptionsWhere[] = [ + { + channel_id: In(visibleChannelIds), + mentions: { id: user.id }, + id: before ? LessThan(before) : undefined, + }, + ]; + if (everyone) { + whereQuery.push({ + channel_id: In(visibleChannelIds), + mention_everyone: true, + id: before ? LessThan(before) : undefined, + }); + } + if (roles) { + whereQuery.push({ + channel_id: In(visibleChannelIds), + mention_roles: { id: In(ownedMentionableRoleIds) }, + id: before ? LessThan(before) : undefined, + }); + } - const sw = Stopwatch.startNew(); - const finalMessages = ( - await Message.find({ - where: whereQuery, - order: { timestamp: "DESC" }, - relations: [ - "author", - "webhook", - "application", - "mentions", - "mention_roles", - "mention_channels", - "sticker_items", - "attachments", - "referenced_message", - "referenced_message.author", - "referenced_message.webhook", - "referenced_message.application", - "referenced_message.mentions", - "referenced_message.mention_roles", - "referenced_message.mention_channels", - "referenced_message.sticker_items", - "referenced_message.attachments", - ], - take: limit, - }) - ).map((m) => { - return { - ...m.toJSON(), - attachments: m.attachments?.map((attachment: Attachment) => - Attachment.prototype.signUrls.call( - attachment, - new NewUrlUserSignatureData({ - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ), - ), - }; - }); + const sw = Stopwatch.startNew(); + const finalMessages = ( + await Message.find({ + where: whereQuery, + order: { timestamp: "DESC" }, + relations: [ + "author", + "webhook", + "application", + "mentions", + "mention_roles", + "mention_channels", + "sticker_items", + "attachments", + "referenced_message", + "referenced_message.author", + "referenced_message.webhook", + "referenced_message.application", + "referenced_message.mentions", + "referenced_message.mention_roles", + "referenced_message.mention_channels", + "referenced_message.sticker_items", + "referenced_message.attachments", + ], + take: limit, + }) + ).map((m) => { + return { + ...m.toJSON(), + attachments: m.attachments?.map((attachment: Attachment) => + Attachment.prototype.signUrls.call( + attachment, + new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ), + ), + }; + }); - console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`); + console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`); - return res.json(finalMessages); - }, + return res.json(finalMessages); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts index c8995d8..4cc4c07 100644 --- a/src/api/routes/users/@me/mfa/codes-verification.ts +++ b/src/api/routes/users/@me/mfa/codes-verification.ts @@ -24,52 +24,52 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "CodesVerificationSchema", - responses: { - 200: { - body: "APIBackupCodeArray", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // const { key, nonce, regenerate } = req.body as CodesVerificationSchema; - const { regenerate } = req.body as CodesVerificationSchema; + "/", + route({ + requestBody: "CodesVerificationSchema", + responses: { + 200: { + body: "APIBackupCodeArray", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // const { key, nonce, regenerate } = req.body as CodesVerificationSchema; + const { regenerate } = req.body as CodesVerificationSchema; - // TODO: We don't have email/etc etc, so can't send a verification code. - // Once that's done, this route can verify `key` + // TODO: We don't have email/etc etc, so can't send a verification code. + // Once that's done, this route can verify `key` - // const user = await User.findOneOrFail({ where: { id: req.user_id } }); - if ((await User.count({ where: { id: req.user_id } })) === 0) throw DiscordApiErrors.UNKNOWN_USER; + // const user = await User.findOneOrFail({ where: { id: req.user_id } }); + if ((await User.count({ where: { id: req.user_id } })) === 0) throw DiscordApiErrors.UNKNOWN_USER; - let codes: BackupCode[]; - if (regenerate) { - await BackupCode.update({ user: { id: req.user_id } }, { expired: true }); + let codes: BackupCode[]; + if (regenerate) { + await BackupCode.update({ user: { id: req.user_id } }, { expired: true }); - codes = generateMfaBackupCodes(req.user_id); - await Promise.all(codes.map((x) => x.save())); - } else { - codes = await BackupCode.find({ - where: { - user: { - id: req.user_id, - }, - expired: false, - }, - }); - } + codes = generateMfaBackupCodes(req.user_id); + await Promise.all(codes.map((x) => x.save())); + } else { + codes = await BackupCode.find({ + where: { + user: { + id: req.user_id, + }, + expired: false, + }, + }); + } - return res.json({ - backup_codes: codes.map((x) => ({ ...x, expired: undefined })), - }); - }, + return res.json({ + backup_codes: codes.map((x) => ({ ...x, expired: undefined })), + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts index 579a106..2eb1a3e 100644 --- a/src/api/routes/users/@me/mfa/codes.ts +++ b/src/api/routes/users/@me/mfa/codes.ts @@ -27,61 +27,61 @@ // TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients router.post( - "/", - route({ - requestBody: "MfaCodesSchema", - deprecated: true, - description: "This route is replaced with users/@me/mfa/codes-verification in newer clients", - responses: { - 200: { - body: "APIBackupCodeArray", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { password, regenerate } = req.body as MfaCodesSchema; + "/", + route({ + requestBody: "MfaCodesSchema", + deprecated: true, + description: "This route is replaced with users/@me/mfa/codes-verification in newer clients", + responses: { + 200: { + body: "APIBackupCodeArray", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { password, regenerate } = req.body as MfaCodesSchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); - if (!(await bcrypt.compare(password, user.data.hash || ""))) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } + if (!(await bcrypt.compare(password, user.data.hash || ""))) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } - let codes: BackupCode[]; - if (regenerate) { - await BackupCode.update({ user: { id: req.user_id } }, { expired: true }); + let codes: BackupCode[]; + if (regenerate) { + await BackupCode.update({ user: { id: req.user_id } }, { expired: true }); - codes = generateMfaBackupCodes(req.user_id); - await Promise.all(codes.map((x) => x.save())); - } else { - codes = await BackupCode.find({ - where: { - user: { - id: req.user_id, - }, - expired: false, - }, - }); - } + codes = generateMfaBackupCodes(req.user_id); + await Promise.all(codes.map((x) => x.save())); + } else { + codes = await BackupCode.find({ + where: { + user: { + id: req.user_id, + }, + expired: false, + }, + }); + } - return res.json({ - backup_codes: codes.map((x) => ({ ...x, expired: undefined })), - }); - }, + return res.json({ + backup_codes: codes.map((x) => ({ ...x, expired: undefined })), + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts index 003dcb7..11c927e 100644 --- a/src/api/routes/users/@me/mfa/totp/disable.ts +++ b/src/api/routes/users/@me/mfa/totp/disable.ts @@ -26,51 +26,51 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "TotpDisableSchema", - responses: { - 200: { - body: "TokenOnlyResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as TotpDisableSchema; + "/", + route({ + requestBody: "TotpDisableSchema", + responses: { + 200: { + body: "TokenOnlyResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as TotpDisableSchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["totp_secret"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["totp_secret"], + }); - const backup = await BackupCode.findOne({ where: { code: body.code } }); - if (!backup) { - const ret = verifyToken(user.totp_secret || "", body.code); - if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - } + const backup = await BackupCode.findOne({ where: { code: body.code } }); + if (!backup) { + const ret = verifyToken(user.totp_secret || "", body.code); + if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + } - await User.update( - { id: req.user_id }, - { - mfa_enabled: false, - totp_secret: "", - }, - ); + await User.update( + { id: req.user_id }, + { + mfa_enabled: false, + totp_secret: "", + }, + ); - await BackupCode.update( - { user: { id: req.user_id } }, - { - expired: true, - }, - ); + await BackupCode.update( + { user: { id: req.user_id } }, + { + expired: true, + }, + ); - return res.json({ - token: await generateToken(user.id), - }); - }, + return res.json({ + token: await generateToken(user.id), + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts index f0563e3..a400c62 100644 --- a/src/api/routes/users/@me/mfa/totp/enable.ts +++ b/src/api/routes/users/@me/mfa/totp/enable.ts @@ -27,54 +27,54 @@ const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "TotpEnableSchema", - responses: { - 200: { - body: "TokenWithBackupCodesResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as TotpEnableSchema; + "/", + route({ + requestBody: "TotpEnableSchema", + responses: { + 200: { + body: "TokenWithBackupCodesResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as TotpEnableSchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data", "email"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data", "email"], + }); - // TODO: Are guests allowed to enable 2fa? - if (user.data.hash) { - if (!(await bcrypt.compare(body.password, user.data.hash))) { - throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); - } - } + // TODO: Are guests allowed to enable 2fa? + if (user.data.hash) { + if (!(await bcrypt.compare(body.password, user.data.hash))) { + throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); + } + } - if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005); + if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005); - if (!body.code) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if (!body.code) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - const backup_codes = generateMfaBackupCodes(req.user_id); - await Promise.all(backup_codes.map((x) => x.save())); - await User.update({ id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret }); + const backup_codes = generateMfaBackupCodes(req.user_id); + await Promise.all(backup_codes.map((x) => x.save())); + await User.update({ id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret }); - res.send({ - token: await generateToken(user.id), - backup_codes: backup_codes.map((x) => ({ - ...x, - expired: undefined, - })), - }); - }, + res.send({ + token: await generateToken(user.id), + backup_codes: backup_codes.map((x) => ({ + ...x, + expired: undefined, + })), + }); + }, ); export default router; diff --git "a/src/api/routes/users/@me/mfa/webauthn/credentials/\043key_id/index.ts" "b/src/api/routes/users/@me/mfa/webauthn/credentials/\043key_id/index.ts" index 6dc0656..77180ca 100644 --- "a/src/api/routes/users/@me/mfa/webauthn/credentials/\043key_id/index.ts" +++ "b/src/api/routes/users/@me/mfa/webauthn/credentials/\043key_id/index.ts" @@ -22,29 +22,29 @@ const router = Router({ mergeParams: true }); router.delete( - "/", - route({ - responses: { - 204: {}, - }, - }), - async (req: Request, res: Response) => { - const { key_id } = req.params; + "/", + route({ + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + const { key_id } = req.params; - await SecurityKey.delete({ - id: key_id, - user_id: req.user_id, - }); + await SecurityKey.delete({ + id: key_id, + user_id: req.user_id, + }); - const keys = await SecurityKey.count({ - where: { user_id: req.user_id }, - }); + const keys = await SecurityKey.count({ + where: { user_id: req.user_id }, + }); - // disable webauthn if there are no keys left - if (keys === 0) await User.update({ id: req.user_id }, { webauthn_enabled: false }); + // disable webauthn if there are no keys left + if (keys === 0) await User.update({ id: req.user_id }, { webauthn_enabled: false }); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts index 0c4b733..7bd29a5 100644 --- a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts +++ b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts @@ -26,138 +26,138 @@ const router = Router({ mergeParams: true }); const isGenerateSchema = (body: WebAuthnPostSchema): body is GenerateWebAuthnCredentialsSchema => { - return "password" in body; + return "password" in body; }; const isCreateSchema = (body: WebAuthnPostSchema): body is CreateWebAuthnCredentialSchema => { - return "credential" in body; + return "credential" in body; }; function toArrayBuffer(buf: Buffer) { - const ab = new ArrayBuffer(buf.length); - const view = new Uint8Array(ab); - for (let i = 0; i < buf.length; ++i) { - view[i] = buf[i]; - } - return ab; + const ab = new ArrayBuffer(buf.length); + const view = new Uint8Array(ab); + for (let i = 0; i < buf.length; ++i) { + view[i] = buf[i]; + } + return ab; } router.get("/", route({}), async (req: Request, res: Response) => { - const securityKeys = await SecurityKey.find({ - where: { - user_id: req.user_id, - }, - }); + const securityKeys = await SecurityKey.find({ + where: { + user_id: req.user_id, + }, + }); - return res.json( - securityKeys.map((key) => ({ - id: key.id, - name: key.name, - })), - ); + return res.json( + securityKeys.map((key) => ({ + id: key.id, + name: key.name, + })), + ); }); router.post( - "/", - route({ - requestBody: "WebAuthnPostSchema", - responses: { - 200: { - body: "WebAuthnCreateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - if (!WebAuthn.fido2) { - // TODO: I did this for typescript and I can't use ! - throw new Error("WebAuthn not enabled"); - } + "/", + route({ + requestBody: "WebAuthnPostSchema", + responses: { + 200: { + body: "WebAuthnCreateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + if (!WebAuthn.fido2) { + // TODO: I did this for typescript and I can't use ! + throw new Error("WebAuthn not enabled"); + } - const user = await User.findOneOrFail({ - where: { - id: req.user_id, - }, - select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "username"], - relations: ["settings"], - }); + const user = await User.findOneOrFail({ + where: { + id: req.user_id, + }, + select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "username"], + relations: ["settings"], + }); - if (isGenerateSchema(req.body)) { - const { password } = req.body; - const same_password = await bcrypt.compare(password, user.data.hash || ""); - if (!same_password) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } + if (isGenerateSchema(req.body)) { + const { password } = req.body; + const same_password = await bcrypt.compare(password, user.data.hash || ""); + if (!same_password) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } - const registrationOptions = await WebAuthn.fido2.attestationOptions(); - const challenge = JSON.stringify({ - publicKey: { - ...registrationOptions, - challenge: Buffer.from(registrationOptions.challenge).toString("base64"), - user: { - id: user.id, - name: user.username, - displayName: user.username, - }, - }, - }); + const registrationOptions = await WebAuthn.fido2.attestationOptions(); + const challenge = JSON.stringify({ + publicKey: { + ...registrationOptions, + challenge: Buffer.from(registrationOptions.challenge).toString("base64"), + user: { + id: user.id, + name: user.username, + displayName: user.username, + }, + }, + }); - const ticket = await generateWebAuthnTicket(challenge); + const ticket = await generateWebAuthnTicket(challenge); - return res.json({ - ticket: ticket, - challenge, - }); - } else if (isCreateSchema(req.body)) { - const { credential, name, ticket } = req.body; + return res.json({ + ticket: ticket, + challenge, + }); + } else if (isCreateSchema(req.body)) { + const { credential, name, ticket } = req.body; - const verified = await verifyWebAuthnToken(ticket); - if (!verified) throw new HTTPError("Invalid ticket", 400); + const verified = await verifyWebAuthnToken(ticket); + if (!verified) throw new HTTPError("Invalid ticket", 400); - const clientAttestationResponse = JSON.parse(credential); + const clientAttestationResponse = JSON.parse(credential); - if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400); + if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400); - const rawIdBuffer = Buffer.from(clientAttestationResponse.rawId, "base64"); - clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer); + const rawIdBuffer = Buffer.from(clientAttestationResponse.rawId, "base64"); + clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer); - const attestationExpectations: ExpectedAttestationResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString()); + const attestationExpectations: ExpectedAttestationResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString()); - const regResult = await WebAuthn.fido2.attestationResult(clientAttestationResponse, { - ...attestationExpectations, - factor: "second", - }); + const regResult = await WebAuthn.fido2.attestationResult(clientAttestationResponse, { + ...attestationExpectations, + factor: "second", + }); - const authnrData = regResult.authnrData; - const keyId = Buffer.from(authnrData.get("credId")).toString("base64"); - const counter = authnrData.get("counter"); - const publicKey = authnrData.get("credentialPublicKeyPem"); + const authnrData = regResult.authnrData; + const keyId = Buffer.from(authnrData.get("credId")).toString("base64"); + const counter = authnrData.get("counter"); + const publicKey = authnrData.get("credentialPublicKeyPem"); - const securityKey = SecurityKey.create({ - name, - counter, - public_key: publicKey, - user_id: req.user_id, - key_id: keyId, - }); + const securityKey = SecurityKey.create({ + name, + counter, + public_key: publicKey, + user_id: req.user_id, + key_id: keyId, + }); - await Promise.all([securityKey.save(), User.update({ id: req.user_id }, { webauthn_enabled: true })]); + await Promise.all([securityKey.save(), User.update({ id: req.user_id }, { webauthn_enabled: true })]); - return res.json({ - name, - id: securityKey.id, - }); - } else { - throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN; - } - }, + return res.json({ + name, + id: securityKey.id, + }); + } else { + throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN; + } + }, ); export default router; diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts index 4969fa7..8cd3330 100644 --- a/src/api/routes/users/@me/notes.ts +++ b/src/api/routes/users/@me/notes.ts @@ -23,89 +23,89 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/:user_id", - route({ - responses: { - 200: { - body: "UserNoteResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req.params; + "/:user_id", + route({ + responses: { + 200: { + body: "UserNoteResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req.params; - const note = await Note.findOneOrFail({ - where: { - owner: { id: req.user_id }, - target: { id: user_id }, - }, - }); + const note = await Note.findOneOrFail({ + where: { + owner: { id: req.user_id }, + target: { id: user_id }, + }, + }); - return res.json({ - note: note?.content, - note_user_id: user_id, - user_id: req.user_id, - }); - }, + return res.json({ + note: note?.content, + note_user_id: user_id, + user_id: req.user_id, + }); + }, ); router.put( - "/:user_id", - route({ - requestBody: "UserNoteUpdateSchema", - responses: { - 204: {}, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req.params; - const owner = await User.findOneOrFail({ where: { id: req.user_id } }); - const target = await User.findOneOrFail({ where: { id: user_id } }); //if noted user does not exist throw - const { note } = req.body; + "/:user_id", + route({ + requestBody: "UserNoteUpdateSchema", + responses: { + 204: {}, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req.params; + const owner = await User.findOneOrFail({ where: { id: req.user_id } }); + const target = await User.findOneOrFail({ where: { id: user_id } }); //if noted user does not exist throw + const { note } = req.body; - if (note && note.length) { - // upsert a note - if ( - await Note.findOne({ - where: { - owner: { id: owner.id }, - target: { id: target.id }, - }, - }) - ) { - Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note }); - } else { - Note.insert({ - id: Snowflake.generate(), - owner, - target, - content: note, - }); - } - } else { - await Note.delete({ - owner: { id: owner.id }, - target: { id: target.id }, - }); - } + if (note && note.length) { + // upsert a note + if ( + await Note.findOne({ + where: { + owner: { id: owner.id }, + target: { id: target.id }, + }, + }) + ) { + Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note }); + } else { + Note.insert({ + id: Snowflake.generate(), + owner, + target, + content: note, + }); + } + } else { + await Note.delete({ + owner: { id: owner.id }, + target: { id: target.id }, + }); + } - await emitEvent({ - event: "USER_NOTE_UPDATE", - data: { - note: note, - id: target.id, - }, - user_id: owner.id, - }); + await emitEvent({ + event: "USER_NOTE_UPDATE", + data: { + note: note, + id: target.id, + }, + user_id: owner.id, + }); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts index d3df9eb..ef6c779 100644 --- a/src/api/routes/users/@me/relationships.ts +++ b/src/api/routes/users/@me/relationships.ts @@ -27,255 +27,255 @@ const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; router.get( - "/", - route({ - responses: { - 200: { - body: "UserRelationshipsResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - relations: ["relationships", "relationships.to"], - select: ["id", "relationships"], - }); + "/", + route({ + responses: { + 200: { + body: "UserRelationshipsResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships", "relationships.to"], + select: ["id", "relationships"], + }); - const related_users = user.relationships.map((r) => r.toPublicRelationship()); - return res.json(related_users); - }, + const related_users = user.relationships.map((r) => r.toPublicRelationship()); + return res.json(related_users); + }, ); router.put( - "/:user_id", - route({ - requestBody: "RelationshipPutSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ - where: { id: req.params.user_id }, - relations: ["relationships", "relationships.to"], - select: userProjection, - }), - req.body.type ?? RelationshipType.friends, - ); - }, + "/:user_id", + route({ + requestBody: "RelationshipPutSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ + where: { id: req.params.user_id }, + relations: ["relationships", "relationships.to"], + select: userProjection, + }), + req.body.type ?? RelationshipType.friends, + ); + }, ); router.post( - "/", - route({ - requestBody: "RelationshipPostSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ - relations: ["relationships", "relationships.to"], - select: userProjection, - where: { - discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes - username: req.body.username, - }, - }), - req.body.type, - ); - }, + "/", + route({ + requestBody: "RelationshipPostSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ + relations: ["relationships", "relationships.to"], + select: userProjection, + where: { + discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes + username: req.body.username, + }, + }), + req.body.type, + ); + }, ); router.delete( - "/:user_id", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req.params; - if (user_id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); + "/:user_id", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req.params; + if (user_id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: userProjection, - relations: ["relationships"], - }); - const friend = await User.findOneOrFail({ - where: { id: user_id }, - select: userProjection, - relations: ["relationships"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: userProjection, + relations: ["relationships"], + }); + const friend = await User.findOneOrFail({ + where: { id: user_id }, + select: userProjection, + relations: ["relationships"], + }); - const relationship = user.relationships.find((x) => x.to_id === user_id); - const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); + const relationship = user.relationships.find((x) => x.to_id === user_id); + const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); - if (!relationship) throw new HTTPError("You are not friends with the user", 404); + if (!relationship) throw new HTTPError("You are not friends with the user", 404); - if (relationship?.type === RelationshipType.blocked) { - // unblock user - await Promise.all([ - Relationship.delete({ id: relationship.id }), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - user_id: req.user_id, - data: relationship.toPublicRelationship(), - } as RelationshipRemoveEvent), - ]); - return res.sendStatus(204); - } - if (friendRequest && friendRequest.type !== RelationshipType.blocked) { - await Promise.all([ - Relationship.delete({ id: friendRequest.id }), - await emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: friendRequest.toPublicRelationship(), - user_id: user_id, - } as RelationshipRemoveEvent), - ]); - } + if (relationship?.type === RelationshipType.blocked) { + // unblock user + await Promise.all([ + Relationship.delete({ id: relationship.id }), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + user_id: req.user_id, + data: relationship.toPublicRelationship(), + } as RelationshipRemoveEvent), + ]); + return res.sendStatus(204); + } + if (friendRequest && friendRequest.type !== RelationshipType.blocked) { + await Promise.all([ + Relationship.delete({ id: friendRequest.id }), + await emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: friendRequest.toPublicRelationship(), + user_id: user_id, + } as RelationshipRemoveEvent), + ]); + } - await Promise.all([ - Relationship.delete({ id: relationship.id }), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: relationship.toPublicRelationship(), - user_id: req.user_id, - } as RelationshipRemoveEvent), - ]); + await Promise.all([ + Relationship.delete({ id: relationship.id }), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: relationship.toPublicRelationship(), + user_id: req.user_id, + } as RelationshipRemoveEvent), + ]); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) { - const id = friend.id; - if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); + const id = friend.id; + if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - relations: ["relationships", "relationships.to"], - select: userProjection, - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships", "relationships.to"], + select: userProjection, + }); - let relationship = user.relationships.find((x) => x.to_id === id); - const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); + let relationship = user.relationships.find((x) => x.to_id === id); + const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); - // TODO: you can add infinitely many blocked users (should this be prevented?) - if (type === RelationshipType.blocked) { - if (relationship) { - if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user"); - relationship.type = RelationshipType.blocked; - await relationship.save(); - } else { - relationship = await Relationship.create({ - to_id: id, - type: RelationshipType.blocked, - from_id: req.user_id, - }).save(); - } + // TODO: you can add infinitely many blocked users (should this be prevented?) + if (type === RelationshipType.blocked) { + if (relationship) { + if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user"); + relationship.type = RelationshipType.blocked; + await relationship.save(); + } else { + relationship = await Relationship.create({ + to_id: id, + type: RelationshipType.blocked, + from_id: req.user_id, + }).save(); + } - if (friendRequest && friendRequest.type !== RelationshipType.blocked) { - await Promise.all([ - Relationship.delete({ id: friendRequest.id }), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: friendRequest.toPublicRelationship(), - user_id: id, - } as RelationshipRemoveEvent), - ]); - } + if (friendRequest && friendRequest.type !== RelationshipType.blocked) { + await Promise.all([ + Relationship.delete({ id: friendRequest.id }), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: friendRequest.toPublicRelationship(), + user_id: id, + } as RelationshipRemoveEvent), + ]); + } - await emitEvent({ - event: "RELATIONSHIP_ADD", - data: relationship.toPublicRelationship(), - user_id: req.user_id, - } as RelationshipAddEvent); + await emitEvent({ + event: "RELATIONSHIP_ADD", + data: relationship.toPublicRelationship(), + user_id: req.user_id, + } as RelationshipAddEvent); - return res.sendStatus(204); - } + return res.sendStatus(204); + } - const { maxFriends } = Config.get().limits.user; - if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); + const { maxFriends } = Config.get().limits.user; + if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); - let incoming_relationship = Relationship.create({ - nickname: undefined, - type: RelationshipType.incoming, - to: user, - from: friend, - }); - let outgoing_relationship = Relationship.create({ - nickname: undefined, - type: RelationshipType.outgoing, - to: friend, - from: user, - }); + let incoming_relationship = Relationship.create({ + nickname: undefined, + type: RelationshipType.incoming, + to: user, + from: friend, + }); + let outgoing_relationship = Relationship.create({ + nickname: undefined, + type: RelationshipType.outgoing, + to: friend, + from: user, + }); - if (friendRequest) { - if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); - // accept friend request - incoming_relationship = friendRequest; - incoming_relationship.type = RelationshipType.friends; - } + if (friendRequest) { + if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); + if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); + // accept friend request + incoming_relationship = friendRequest; + incoming_relationship.type = RelationshipType.friends; + } - if (relationship) { - if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request"); - if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request"); - if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); - outgoing_relationship = relationship; - outgoing_relationship.type = RelationshipType.friends; - } + if (relationship) { + if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request"); + if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request"); + if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); + outgoing_relationship = relationship; + outgoing_relationship.type = RelationshipType.friends; + } - await Promise.all([ - incoming_relationship.save(), - outgoing_relationship.save(), - emitEvent({ - event: "RELATIONSHIP_ADD", - data: outgoing_relationship.toPublicRelationship(), - user_id: req.user_id, - } as RelationshipAddEvent), - emitEvent({ - event: "RELATIONSHIP_ADD", - data: { - ...incoming_relationship.toPublicRelationship(), - should_notify: true, - }, - user_id: id, - } as RelationshipAddEvent), - ]); + await Promise.all([ + incoming_relationship.save(), + outgoing_relationship.save(), + emitEvent({ + event: "RELATIONSHIP_ADD", + data: outgoing_relationship.toPublicRelationship(), + user_id: req.user_id, + } as RelationshipAddEvent), + emitEvent({ + event: "RELATIONSHIP_ADD", + data: { + ...incoming_relationship.toPublicRelationship(), + should_notify: true, + }, + user_id: id, + } as RelationshipAddEvent), + ]); - return res.sendStatus(204); + return res.sendStatus(204); } diff --git a/src/api/routes/users/@me/settings-proto/1.ts b/src/api/routes/users/@me/settings-proto/1.ts index 6f58b42..d54bdf3 100644 --- a/src/api/routes/users/@me/settings-proto/1.ts +++ b/src/api/routes/users/@me/settings-proto/1.ts @@ -27,159 +27,159 @@ //#region Protobuf router.get( - "/", - route({ - responses: { - 200: { - body: "SettingsProtoResponse", - }, - }, - query: { - atomic: { - type: "boolean", - description: "Whether to try to apply the settings update atomically (default false)", - }, - }, - }), - async (req: Request, res: Response) => { - const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); + "/", + route({ + responses: { + 200: { + body: "SettingsProtoResponse", + }, + }, + query: { + atomic: { + type: "boolean", + description: "Whether to try to apply the settings update atomically (default false)", + }, + }, + }), + async (req: Request, res: Response) => { + const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); - res.json({ - settings: PreloadedUserSettings.toBase64(userSettings.userSettings!), - } as SettingsProtoResponse); - }, + res.json({ + settings: PreloadedUserSettings.toBase64(userSettings.userSettings!), + } as SettingsProtoResponse); + }, ); router.patch( - "/", - route({ - requestBody: "SettingsProtoUpdateSchema", - responses: { - 200: { - body: "SettingsProtoUpdateResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema; - const { atomic } = req.query; - const updatedSettings = PreloadedUserSettings.fromBase64(settings); + "/", + route({ + requestBody: "SettingsProtoUpdateSchema", + responses: { + 200: { + body: "SettingsProtoUpdateResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema; + const { atomic } = req.query; + const updatedSettings = PreloadedUserSettings.fromBase64(settings); - const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true"); + const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true"); - res.json({ - settings: PreloadedUserSettings.toBase64(resultObj.settings), - out_of_date: resultObj.out_of_date, - }); - }, + res.json({ + settings: PreloadedUserSettings.toBase64(resultObj.settings), + out_of_date: resultObj.out_of_date, + }); + }, ); //#endregion //#region JSON router.get( - "/json", - route({ - responses: { - 200: { - body: "SettingsProtoJsonResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); + "/json", + route({ + responses: { + 200: { + body: "SettingsProtoJsonResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); - res.json({ - settings: PreloadedUserSettings.toJson(userSettings.userSettings!), - } as SettingsProtoJsonResponse); - }, + res.json({ + settings: PreloadedUserSettings.toJson(userSettings.userSettings!), + } as SettingsProtoJsonResponse); + }, ); router.patch( - "/json", - route({ - requestBody: "SettingsProtoUpdateJsonSchema", - responses: { - 200: { - body: "SettingsProtoUpdateJsonResponse", - }, - }, - query: { - atomic: { - type: "boolean", - description: "Whether to try to apply the settings update atomically (default false)", - }, - }, - }), - async (req: Request, res: Response) => { - const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema; - const { atomic } = req.query; - const updatedSettings = PreloadedUserSettings.fromJson(settings); + "/json", + route({ + requestBody: "SettingsProtoUpdateJsonSchema", + responses: { + 200: { + body: "SettingsProtoUpdateJsonResponse", + }, + }, + query: { + atomic: { + type: "boolean", + description: "Whether to try to apply the settings update atomically (default false)", + }, + }, + }), + async (req: Request, res: Response) => { + const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema; + const { atomic } = req.query; + const updatedSettings = PreloadedUserSettings.fromJson(settings); - const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true"); + const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true"); - res.json({ - settings: PreloadedUserSettings.toJson(resultObj.settings), - out_of_date: resultObj.out_of_date, - }); - }, + res.json({ + settings: PreloadedUserSettings.toJson(resultObj.settings), + out_of_date: resultObj.out_of_date, + }); + }, ); //#endregion async function patchUserSettings(userId: string, updatedSettings: PreloadedUserSettings, required_data_version: number | undefined, atomic: boolean = false) { - const userSettings = await UserSettingsProtos.getOrDefault(userId); - let settings = userSettings.userSettings!; + const userSettings = await UserSettingsProtos.getOrDefault(userId); + let settings = userSettings.userSettings!; - if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) { - return { - settings: settings, - out_of_date: true, - }; - } + if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) { + return { + settings: settings, + out_of_date: true, + }; + } - if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_SETTINGS_UPDATES) && process.env.LOG_PROTO_SETTINGS_UPDATES !== "false") - console.log(`Updating user settings for user ${userId} with atomic=${atomic}:`, updatedSettings); + if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_SETTINGS_UPDATES) && process.env.LOG_PROTO_SETTINGS_UPDATES !== "false") + console.log(`Updating user settings for user ${userId} with atomic=${atomic}:`, updatedSettings); - if (!atomic) { - settings = PreloadedUserSettings.fromJson( - Object.assign(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue, - ); - } else { - settings = PreloadedUserSettings.fromJson( - OrmUtils.mergeDeep(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue, - ); - } + if (!atomic) { + settings = PreloadedUserSettings.fromJson( + Object.assign(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue, + ); + } else { + settings = PreloadedUserSettings.fromJson( + OrmUtils.mergeDeep(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue, + ); + } - settings.versions = { - clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0, - serverVersion: settings.versions?.serverVersion ?? 0, - dataVersion: (settings.versions?.dataVersion ?? 0) + 1, - }; - userSettings.userSettings = settings; - await userSettings.save(); + settings.versions = { + clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0, + serverVersion: settings.versions?.serverVersion ?? 0, + dataVersion: (settings.versions?.dataVersion ?? 0) + 1, + }; + userSettings.userSettings = settings; + await userSettings.save(); - await emitEvent({ - user_id: userId, - event: "USER_SETTINGS_PROTO_UPDATE", - data: { - settings: { - proto: PreloadedUserSettings.toBase64(settings), - type: 1, - }, - json_settings: { - proto: PreloadedUserSettings.toJson(settings), - type: "user_settings", - }, - partial: false, // Unsure how this should behave - }, - }); - // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent - // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now. + await emitEvent({ + user_id: userId, + event: "USER_SETTINGS_PROTO_UPDATE", + data: { + settings: { + proto: PreloadedUserSettings.toBase64(settings), + type: 1, + }, + json_settings: { + proto: PreloadedUserSettings.toJson(settings), + type: "user_settings", + }, + partial: false, // Unsure how this should behave + }, + }); + // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent + // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now. - return { - settings: settings, - out_of_date: false, - }; + return { + settings: settings, + out_of_date: false, + }; } export default router; diff --git a/src/api/routes/users/@me/settings-proto/2.ts b/src/api/routes/users/@me/settings-proto/2.ts index b37805f..809db84 100644 --- a/src/api/routes/users/@me/settings-proto/2.ts +++ b/src/api/routes/users/@me/settings-proto/2.ts @@ -27,159 +27,159 @@ //#region Protobuf router.get( - "/", - route({ - responses: { - 200: { - body: "SettingsProtoResponse", - }, - }, - query: { - atomic: { - type: "boolean", - description: "Whether to try to apply the settings update atomically (default false)", - }, - }, - }), - async (req: Request, res: Response) => { - const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); + "/", + route({ + responses: { + 200: { + body: "SettingsProtoResponse", + }, + }, + query: { + atomic: { + type: "boolean", + description: "Whether to try to apply the settings update atomically (default false)", + }, + }, + }), + async (req: Request, res: Response) => { + const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); - res.json({ - settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!), - } as SettingsProtoResponse); - }, + res.json({ + settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!), + } as SettingsProtoResponse); + }, ); router.patch( - "/", - route({ - requestBody: "SettingsProtoUpdateSchema", - responses: { - 200: { - body: "SettingsProtoUpdateResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema; - const { atomic } = req.query; - const updatedSettings = FrecencyUserSettings.fromBase64(settings); + "/", + route({ + requestBody: "SettingsProtoUpdateSchema", + responses: { + 200: { + body: "SettingsProtoUpdateResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema; + const { atomic } = req.query; + const updatedSettings = FrecencyUserSettings.fromBase64(settings); - const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true"); + const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true"); - res.json({ - settings: FrecencyUserSettings.toBase64(resultObj.settings), - out_of_date: resultObj.out_of_date, - }); - }, + res.json({ + settings: FrecencyUserSettings.toBase64(resultObj.settings), + out_of_date: resultObj.out_of_date, + }); + }, ); //#endregion //#region JSON router.get( - "/json", - route({ - responses: { - 200: { - body: "SettingsProtoJsonResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); + "/json", + route({ + responses: { + 200: { + body: "SettingsProtoJsonResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); - res.json({ - settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!), - } as SettingsProtoJsonResponse); - }, + res.json({ + settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!), + } as SettingsProtoJsonResponse); + }, ); router.patch( - "/json", - route({ - requestBody: "SettingsProtoUpdateJsonSchema", - responses: { - 200: { - body: "SettingsProtoUpdateJsonResponse", - }, - }, - query: { - atomic: { - type: "boolean", - description: "Whether to try to apply the settings update atomically (default false)", - }, - }, - }), - async (req: Request, res: Response) => { - const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema; - const { atomic } = req.query; - const updatedSettings = FrecencyUserSettings.fromJson(settings); + "/json", + route({ + requestBody: "SettingsProtoUpdateJsonSchema", + responses: { + 200: { + body: "SettingsProtoUpdateJsonResponse", + }, + }, + query: { + atomic: { + type: "boolean", + description: "Whether to try to apply the settings update atomically (default false)", + }, + }, + }), + async (req: Request, res: Response) => { + const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema; + const { atomic } = req.query; + const updatedSettings = FrecencyUserSettings.fromJson(settings); - const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true"); + const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true"); - res.json({ - settings: FrecencyUserSettings.toJson(resultObj.settings), - out_of_date: resultObj.out_of_date, - }); - }, + res.json({ + settings: FrecencyUserSettings.toJson(resultObj.settings), + out_of_date: resultObj.out_of_date, + }); + }, ); //#endregion async function patchUserSettings(userId: string, updatedSettings: FrecencyUserSettings, required_data_version: number | undefined, atomic: boolean = false) { - const userSettings = await UserSettingsProtos.getOrDefault(userId); - let settings = userSettings.frecencySettings!; + const userSettings = await UserSettingsProtos.getOrDefault(userId); + let settings = userSettings.frecencySettings!; - if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) { - return { - settings: settings, - out_of_date: true, - }; - } + if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) { + return { + settings: settings, + out_of_date: true, + }; + } - if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_FRECENCY_UPDATES) && process.env.LOG_PROTO_FRECENCY_UPDATES !== "false") - console.log(`Updating frecency settings for user ${userId} with atomic=${atomic}:`, updatedSettings); + if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_FRECENCY_UPDATES) && process.env.LOG_PROTO_FRECENCY_UPDATES !== "false") + console.log(`Updating frecency settings for user ${userId} with atomic=${atomic}:`, updatedSettings); - if (!atomic) { - settings = FrecencyUserSettings.fromJson( - Object.assign(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue, - ); - } else { - settings = FrecencyUserSettings.fromJson( - OrmUtils.mergeDeep(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue, - ); - } + if (!atomic) { + settings = FrecencyUserSettings.fromJson( + Object.assign(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue, + ); + } else { + settings = FrecencyUserSettings.fromJson( + OrmUtils.mergeDeep(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue, + ); + } - settings.versions = { - clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0, - serverVersion: settings.versions?.serverVersion ?? 0, - dataVersion: (settings.versions?.dataVersion ?? 0) + 1, - }; - userSettings.frecencySettings = settings; - await userSettings.save(); + settings.versions = { + clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0, + serverVersion: settings.versions?.serverVersion ?? 0, + dataVersion: (settings.versions?.dataVersion ?? 0) + 1, + }; + userSettings.frecencySettings = settings; + await userSettings.save(); - await emitEvent({ - user_id: userId, - event: "USER_SETTINGS_PROTO_UPDATE", - data: { - settings: { - proto: FrecencyUserSettings.toBase64(settings), - type: 2, - }, - json_settings: { - proto: FrecencyUserSettings.toJson(settings), - type: "frecency_settings", - }, - partial: false, // Unsure how this should behave - }, - }); - // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent - // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now. + await emitEvent({ + user_id: userId, + event: "USER_SETTINGS_PROTO_UPDATE", + data: { + settings: { + proto: FrecencyUserSettings.toBase64(settings), + type: 2, + }, + json_settings: { + proto: FrecencyUserSettings.toJson(settings), + type: "frecency_settings", + }, + partial: false, // Unsure how this should behave + }, + }); + // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent + // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now. - return { - settings: settings, - out_of_date: false, - }; + return { + settings: settings, + out_of_date: false, + }; } export default router; diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts index d220b85..57ffba1 100644 --- a/src/api/routes/users/@me/settings.ts +++ b/src/api/routes/users/@me/settings.ts @@ -24,81 +24,81 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "UserSettings", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const settings = await UserSettings.getOrDefault(req.user_id); - return res.json(settings); - }, + "/", + route({ + responses: { + 200: { + body: "UserSettings", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const settings = await UserSettings.getOrDefault(req.user_id); + return res.json(settings); + }, ); router.patch( - "/", - route({ - requestBody: "UserSettingsUpdateSchema", - responses: { - 200: { - body: "UserSettings", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as UserSettingsUpdateSchema; - if (!body) return res.status(400).json({ code: 400, message: "Invalid request body" }); - if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unknown locale + "/", + route({ + requestBody: "UserSettingsUpdateSchema", + responses: { + 200: { + body: "UserSettings", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as UserSettingsUpdateSchema; + if (!body) return res.status(400).json({ code: 400, message: "Invalid request body" }); + if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unknown locale - const user = await User.findOneOrFail({ - where: { id: req.user_id, bot: false }, - relations: ["settings"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id, bot: false }, + relations: ["settings"], + }); - if (!user.settings) user.settings = UserSettings.create(body); - else user.settings.assign(body); + if (!user.settings) user.settings = UserSettings.create(body); + else user.settings.assign(body); - if (body.guild_folders) user.settings.guild_folders = body.guild_folders; + if (body.guild_folders) user.settings.guild_folders = body.guild_folders; - await user.settings.save(); - await user.save(); - if (body.status) { - const [session] = (await Session.find({ - where: { user_id: user.id }, - })) as [Session | undefined]; - if (session) { - session.status = body.status; + await user.settings.save(); + await user.save(); + if (body.status) { + const [session] = (await Session.find({ + where: { user_id: user.id }, + })) as [Session | undefined]; + if (session) { + session.status = body.status; - await Promise.all([ - emitEvent({ - event: "PRESENCE_UPDATE", - user_id: user.id, - data: { - user: user.toPublicUser(), - activities: session.activities, - client_status: session?.client_status, - status: session.getPublicStatus(), - }, - } as PresenceUpdateEvent), - session.save(), - ]); - } - } + await Promise.all([ + emitEvent({ + event: "PRESENCE_UPDATE", + user_id: user.id, + data: { + user: user.toPublicUser(), + activities: session.activities, + client_status: session?.client_status, + status: session.getPublicStatus(), + }, + } as PresenceUpdateEvent), + session.save(), + ]); + } + } - res.json({ ...user.settings, index: undefined }); - }, + res.json({ ...user.settings, index: undefined }); + }, ); export default router; diff --git a/src/api/routes/voice/regions.ts b/src/api/routes/voice/regions.ts index ba26123..a6759b5 100644 --- a/src/api/routes/voice/regions.ts +++ b/src/api/routes/voice/regions.ts @@ -22,17 +22,17 @@ const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIGuildVoiceRegion", - }, - }, - }), - async (req: Request, res: Response) => { - res.json(await getVoiceRegions(req.ip!, true)); //vip true? - }, + "/", + route({ + responses: { + 200: { + body: "APIGuildVoiceRegion", + }, + }, + }), + async (req: Request, res: Response) => { + res.json(await getVoiceRegions(req.ip!, true)); //vip true? + }, ); export default router; diff --git "a/src/api/routes/webhooks/\043webhook_id/\043token/github.ts" "b/src/api/routes/webhooks/\043webhook_id/\043token/github.ts" index 78d6bbb..81aa40a 100644 --- "a/src/api/routes/webhooks/\043webhook_id/\043token/github.ts" +++ "b/src/api/routes/webhooks/\043webhook_id/\043token/github.ts" @@ -7,402 +7,402 @@ const router = Router({ mergeParams: true }); const parseGitHubWebhook = (req: Request, res: Response, next: NextFunction) => { - const eventType = req.headers["x-github-event"] as string; - if (!eventType) { - throw new HTTPError("Missing X-GitHub-Event header", 400); - } + const eventType = req.headers["x-github-event"] as string; + if (!eventType) { + throw new HTTPError("Missing X-GitHub-Event header", 400); + } - const discordPayload: WebhookExecuteSchema = { - username: "GitHub", - avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png", - }; + const discordPayload: WebhookExecuteSchema = { + username: "GitHub", + avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png", + }; - switch (eventType) { - case "commit_comment": - if (req.body.action !== "created") { - return; - } + switch (eventType) { + case "commit_comment": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New comment on commit \`${req.body.comment.commit_id.slice(0, 7)}\``, - description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, - url: req.body.comment.html_url, - }, - ]; - break; - case "create": - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New ${req.body.ref_type} created: ${req.body.ref}`, - }, - ]; - break; - case "delete": - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] ${req.body.ref_type} deleted: ${req.body.ref}`, - }, - ]; - break; - case "fork": - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Fork created: ${req.body.forkee.full_name}`, - url: req.body.forkee.html_url, - }, - ]; - break; - case "issue_comment": - if (req.body.action !== "created") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New comment on commit \`${req.body.comment.commit_id.slice(0, 7)}\``, + description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, + url: req.body.comment.html_url, + }, + ]; + break; + case "create": + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New ${req.body.ref_type} created: ${req.body.ref}`, + }, + ]; + break; + case "delete": + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] ${req.body.ref_type} deleted: ${req.body.ref}`, + }, + ]; + break; + case "fork": + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Fork created: ${req.body.forkee.full_name}`, + url: req.body.forkee.html_url, + }, + ]; + break; + case "issue_comment": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - color: "pull_request" in req.body.issue ? 12576191 : 15109472, - title: `[${req.body.repository.full_name}] New comment on ${"pull_request" in req.body.issue ? "pull request" : "issue"} #${req.body.issue.number}: ${ - req.body.issue.title.length > 150 ? `${req.body.issue.title.slice(0, 147)}...` : req.body.issue.title - }`, - url: req.body.comment.html_url, - description: req.body.comment.body.length > 150 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, - }, - ]; - break; - case "issues": - if (!["opened", "closed"].includes(req.body.action)) { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + color: "pull_request" in req.body.issue ? 12576191 : 15109472, + title: `[${req.body.repository.full_name}] New comment on ${"pull_request" in req.body.issue ? "pull request" : "issue"} #${req.body.issue.number}: ${ + req.body.issue.title.length > 150 ? `${req.body.issue.title.slice(0, 147)}...` : req.body.issue.title + }`, + url: req.body.comment.html_url, + description: req.body.comment.body.length > 150 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, + }, + ]; + break; + case "issues": + if (!["opened", "closed"].includes(req.body.action)) { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Issue ${req.body.action} #${req.body.issue.number}: ${req.body.issue.title}`, - url: req.body.issue.html_url, - }, - ]; + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Issue ${req.body.action} #${req.body.issue.number}: ${req.body.issue.title}`, + url: req.body.issue.html_url, + }, + ]; - if (req.body.action === "opened") { - discordPayload.embeds[0].color = 15426592; - discordPayload.embeds[0].description = req.body.issue.body.length > 150 ? `${req.body.issue.body.slice(0, 147)}...` : req.body.issue.body; - } - break; - case "member": - if (req.body.action !== "added") { - return; - } + if (req.body.action === "opened") { + discordPayload.embeds[0].color = 15426592; + discordPayload.embeds[0].description = req.body.issue.body.length > 150 ? `${req.body.issue.body.slice(0, 147)}...` : req.body.issue.body; + } + break; + case "member": + if (req.body.action !== "added") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New collaborator added: ${req.body.member.login}`, - url: req.body.member.html_url, - }, - ]; - break; - case "public": - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Now open sourced!`, - }, - ]; - break; - case "pull_request": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord - if (!["opened", "closed"].includes(req.body.action)) { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New collaborator added: ${req.body.member.login}`, + url: req.body.member.html_url, + }, + ]; + break; + case "public": + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Now open sourced!`, + }, + ]; + break; + case "pull_request": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord + if (!["opened", "closed"].includes(req.body.action)) { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Pull request ${req.body.action}: #${req.body.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, - url: req.body.pull_request.html_url, - }, - ]; + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Pull request ${req.body.action}: #${req.body.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, + url: req.body.pull_request.html_url, + }, + ]; - if (req.body.action === "opened") { - if (req.body.pull_request.body != null) { - discordPayload.embeds[0].description = req.body.pull_request.body.length > 500 ? `${req.body.pull_request.body.slice(0, 497)}...` : req.body.pull_request.body; - } - discordPayload.embeds[0].color = 38912; - } - break; - case "pull_request_review": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord - if (req.body.action !== "submitted") { - return; - } + if (req.body.action === "opened") { + if (req.body.pull_request.body != null) { + discordPayload.embeds[0].description = req.body.pull_request.body.length > 500 ? `${req.body.pull_request.body.slice(0, 497)}...` : req.body.pull_request.body; + } + discordPayload.embeds[0].color = 38912; + } + break; + case "pull_request_review": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord + if (req.body.action !== "submitted") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Pull request review submitted: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, - description: req.body.review.body.length > 500 ? `${req.body.review.body.slice(0, 497)}...` : req.body.review.body, - url: req.body.review.html_url, - }, - ]; - break; - case "pull_request_review_comment": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord - if (req.body.action !== "created") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Pull request review submitted: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, + description: req.body.review.body.length > 500 ? `${req.body.review.body.slice(0, 497)}...` : req.body.review.body, + url: req.body.review.html_url, + }, + ]; + break; + case "pull_request_review_comment": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - color: 12576191, - title: `[${req.body.repository.full_name}] New review comment on pull request: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, - description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, - url: req.body.comment.html_url, - }, - ]; - break; - case "push": - if (!req.body.ref.startsWith("refs/heads/")) { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + color: 12576191, + title: `[${req.body.repository.full_name}] New review comment on pull request: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, + description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, + url: req.body.comment.html_url, + }, + ]; + break; + case "push": + if (!req.body.ref.startsWith("refs/heads/")) { + return; + } - if (req.body.forced) { - discordPayload.embeds = [ - { - color: 16525609, - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.name}] Branch ${req.body.ref.slice(11)} was force-pushed to \`${req.body.head_commit.id.slice(0, 7)}\``, - description: `[Compare changes](${req.body.compare})`, - }, - ]; - } else { - discordPayload.embeds = [ - { - color: 7506394, - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.name}:${req.body.ref.slice(11)}] ${req.body.commits.length} new commit${req.body.commits.length > 1 ? "s" : ""}`, - url: req.body.commits.length > 1 ? req.body.compare : req.body.head_commit.url, - description: req.body.commits - .slice(0, 5) // Discord only shows 5 first commits - .map( - (c: { id: string; url: string; message: string; author: { username: string } }) => - `[\`${c.id.slice(0, 7)}\`](${c.url}) ${c.message.split("\n")[0].length > 46 ? `${c.message.slice(0, 47)}...` : c.message.split("\n")[0]} - ${c.author.username}`, - ) - .join("\n"), - }, - ]; - } - break; - case "release": - if (req.body.action !== "created") { - return; - } + if (req.body.forced) { + discordPayload.embeds = [ + { + color: 16525609, + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.name}] Branch ${req.body.ref.slice(11)} was force-pushed to \`${req.body.head_commit.id.slice(0, 7)}\``, + description: `[Compare changes](${req.body.compare})`, + }, + ]; + } else { + discordPayload.embeds = [ + { + color: 7506394, + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.name}:${req.body.ref.slice(11)}] ${req.body.commits.length} new commit${req.body.commits.length > 1 ? "s" : ""}`, + url: req.body.commits.length > 1 ? req.body.compare : req.body.head_commit.url, + description: req.body.commits + .slice(0, 5) // Discord only shows 5 first commits + .map( + (c: { id: string; url: string; message: string; author: { username: string } }) => + `[\`${c.id.slice(0, 7)}\`](${c.url}) ${c.message.split("\n")[0].length > 46 ? `${c.message.slice(0, 47)}...` : c.message.split("\n")[0]} - ${c.author.username}`, + ) + .join("\n"), + }, + ]; + } + break; + case "release": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New release published: ${req.body.release.tag_name}`, - url: req.body.release.html_url, - }, - ]; - break; - case "watch": - if (req.body.action !== "started") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New release published: ${req.body.release.tag_name}`, + url: req.body.release.html_url, + }, + ]; + break; + case "watch": + if (req.body.action !== "started") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New star added`, - url: req.body.repository.html_url, - }, - ]; - break; - case "check_run": - if (req.body.action !== "completed") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New star added`, + url: req.body.repository.html_url, + }, + ]; + break; + case "check_run": + if (req.body.action !== "completed") { + return; + } - discordPayload.embeds = [ - { - color: req.body.check_run.conclusion == "success" ? 38912 : 16525609, - title: `[${req.body.repository.name}] ${req.body.check_run.name} ${req.body.check_run.conclusion} on ${req.body.check_run.check_suite.head_branch}`, - url: req.body.check_run.html_url, - }, - ]; - break; - case "check_suite": - if (req.body.action !== "completed") { - return; - } + discordPayload.embeds = [ + { + color: req.body.check_run.conclusion == "success" ? 38912 : 16525609, + title: `[${req.body.repository.name}] ${req.body.check_run.name} ${req.body.check_run.conclusion} on ${req.body.check_run.check_suite.head_branch}`, + url: req.body.check_run.html_url, + }, + ]; + break; + case "check_suite": + if (req.body.action !== "completed") { + return; + } - discordPayload.embeds = [ - { - color: req.body.check_suite.conclusion == "success" ? 38912 : 16525609, - title: `[${req.body.repository.name}] GitHub Actions checks ${req.body.check_suite.conclusion} on ${req.body.check_suite.head_branch}`, - url: `https://github.com/${req.body.repository.full_name}/commit/${req.body.check_suite.head_commit.id}`, - }, - ]; - break; - case "discussion": - if (req.body.action !== "created") { - return; - } + discordPayload.embeds = [ + { + color: req.body.check_suite.conclusion == "success" ? 38912 : 16525609, + title: `[${req.body.repository.name}] GitHub Actions checks ${req.body.check_suite.conclusion} on ${req.body.check_suite.head_branch}`, + url: `https://github.com/${req.body.repository.full_name}/commit/${req.body.check_suite.head_commit.id}`, + }, + ]; + break; + case "discussion": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - color: 15109472, - title: `[${req.body.repository.name}] New discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`, - url: req.body.discussion.html_url, - description: req.body.discussion.body.length > 500 ? `${req.body.discussion.body.slice(0, 497)}...` : req.body.discussion.body, - }, - ]; - break; - case "discussion_comment": - if (req.body.action !== "created") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + color: 15109472, + title: `[${req.body.repository.name}] New discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`, + url: req.body.discussion.html_url, + description: req.body.discussion.body.length > 500 ? `${req.body.discussion.body.slice(0, 497)}...` : req.body.discussion.body, + }, + ]; + break; + case "discussion_comment": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - color: 15109472, - title: `[${req.body.comment.repository_url}] New comment on discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`, - url: req.body.comment.html_url, - description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, - }, - ]; - break; - default: - return res.status(204).end(); // Yes, discord sends 204 even on invalid event - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + color: 15109472, + title: `[${req.body.comment.repository_url}] New comment on discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`, + url: req.body.comment.html_url, + description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, + }, + ]; + break; + default: + return res.status(204).end(); // Yes, discord sends 204 even on invalid event + } - req.body = discordPayload; - req.query.wait ||= "true"; + req.body = discordPayload; + req.query.wait ||= "true"; - next(); + next(); }; router.post( - "/", - parseGitHubWebhook, - (req, _res, next) => { - if (req.body.payload_json) { - req.body = JSON.parse(req.body.payload_json); - } - next(); - }, - route({ - requestBody: "WebhookExecuteSchema", - query: { - wait: { - type: "boolean", - required: false, - description: "waits for server confirmation of message send before response, and returns the created message body", - }, - thread_id: { - type: "string", - required: false, - description: "Send a message to the specified thread within a webhook's channel.", - }, - }, - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - executeWebhook, + "/", + parseGitHubWebhook, + (req, _res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); + } + next(); + }, + route({ + requestBody: "WebhookExecuteSchema", + query: { + wait: { + type: "boolean", + required: false, + description: "waits for server confirmation of message send before response, and returns the created message body", + }, + thread_id: { + type: "string", + required: false, + description: "Send a message to the specified thread within a webhook's channel.", + }, + }, + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + executeWebhook, ); export default router; diff --git "a/src/api/routes/webhooks/\043webhook_id/\043token/index.ts" "b/src/api/routes/webhooks/\043webhook_id/\043token/index.ts" index 1b589a7..eecbfca 100644 --- "a/src/api/routes/webhooks/\043webhook_id/\043token/index.ts" +++ "b/src/api/routes/webhooks/\043webhook_id/\043token/index.ts" @@ -8,179 +8,179 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - description: "Returns a webhook object for the given id and token.", - responses: { - 200: { - body: "APIWebhook", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id, token } = req.params; - const webhook = await Webhook.findOne({ - where: { - id: webhook_id, - }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + "/", + route({ + description: "Returns a webhook object for the given id and token.", + responses: { + 200: { + body: "APIWebhook", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id, token } = req.params; + const webhook = await Webhook.findOne({ + where: { + id: webhook_id, + }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - if (!webhook) { - throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } + if (!webhook) { + throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } - if (webhook.token !== token) { - throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; - } + if (webhook.token !== token) { + throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; + } - return res.json({ - ...webhook, - url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, - }); - }, + return res.json({ + ...webhook, + url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, + }); + }, ); // TODO: config max upload size const messageUpload = multer({ - limits: { - fileSize: Config.get().limits.message.maxAttachmentSize, - fields: 10, - // files: 1 - }, - storage: multer.memoryStorage(), + limits: { + fileSize: Config.get().limits.message.maxAttachmentSize, + fields: 10, + // files: 1 + }, + storage: multer.memoryStorage(), }); // max upload 50 mb // https://discord.com/developers/docs/resources/webhook#execute-webhook // TODO: Slack compatible hooks router.post( - "/", - messageUpload.any(), - (req, _res, next) => { - if (req.body.payload_json) { - req.body = JSON.parse(req.body.payload_json); - } + "/", + messageUpload.any(), + (req, _res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); + } - next(); - }, - route({ - requestBody: "WebhookExecuteSchema", - query: { - wait: { - type: "boolean", - required: false, - description: "waits for server confirmation of message send before response, and returns the created message body", - }, - thread_id: { - type: "string", - required: false, - description: "Send a message to the specified thread within a webhook's channel.", - }, - }, - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - executeWebhook, + next(); + }, + route({ + requestBody: "WebhookExecuteSchema", + query: { + wait: { + type: "boolean", + required: false, + description: "waits for server confirmation of message send before response, and returns the created message body", + }, + thread_id: { + type: "string", + required: false, + description: "Send a message to the specified thread within a webhook's channel.", + }, + }, + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + executeWebhook, ); router.delete( - "/", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id, token } = req.params; + "/", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id, token } = req.params; - const webhook = await Webhook.findOne({ - where: { - id: webhook_id, - }, - relations: ["channel", "guild", "application"], - }); + const webhook = await Webhook.findOne({ + where: { + id: webhook_id, + }, + relations: ["channel", "guild", "application"], + }); - if (!webhook) { - throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } + if (!webhook) { + throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } - if (webhook.token !== token) { - throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; - } - const channel_id = webhook.channel_id; - await Webhook.delete({ id: webhook_id }); + if (webhook.token !== token) { + throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; + } + const channel_id = webhook.channel_id; + await Webhook.delete({ id: webhook_id }); - await emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: webhook.guild_id, - }, - } as WebhooksUpdateEvent); + await emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: webhook.guild_id, + }, + } as WebhooksUpdateEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.patch( - "/", - route({ - requestBody: "WebhookUpdateSchema", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id, token } = req.params; - const body = req.body as WebhookUpdateSchema; + "/", + route({ + requestBody: "WebhookUpdateSchema", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id, token } = req.params; + const body = req.body as WebhookUpdateSchema; - const webhook = await Webhook.findOneOrFail({ - where: { id: webhook_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); - const channel_id = webhook.channel_id; - if (!body.name && !body.avatar) { - throw new HTTPError("Empty webhook updates are not allowed", 50006); - } - if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string); + const webhook = await Webhook.findOneOrFail({ + where: { id: webhook_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); + const channel_id = webhook.channel_id; + if (!body.name && !body.avatar) { + throw new HTTPError("Empty webhook updates are not allowed", 50006); + } + if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string); - if (body.name) { - ValidateName(body.name); - } + if (body.name) { + ValidateName(body.name); + } - webhook.assign(body); + webhook.assign(body); - await Promise.all([ - webhook.save(), - emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: webhook.guild_id, - }, - } as WebhooksUpdateEvent), - ]); - res.status(204); - }, + await Promise.all([ + webhook.save(), + emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: webhook.guild_id, + }, + } as WebhooksUpdateEvent), + ]); + res.status(204); + }, ); export default router; diff --git "a/src/api/routes/webhooks/\043webhook_id/index.ts" "b/src/api/routes/webhooks/\043webhook_id/index.ts" index 9c0a641..8844b8e 100644 --- "a/src/api/routes/webhooks/\043webhook_id/index.ts" +++ "b/src/api/routes/webhooks/\043webhook_id/index.ts" @@ -6,141 +6,141 @@ const router = Router({ mergeParams: true }); router.get( - "/", - route({ - description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.", - responses: { - 200: { - body: "APIWebhook", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id } = req.params; - const webhook = await Webhook.findOneOrFail({ - where: { id: webhook_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + "/", + route({ + description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.", + responses: { + 200: { + body: "APIWebhook", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id } = req.params; + const webhook = await Webhook.findOneOrFail({ + where: { id: webhook_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - if (webhook.guild_id) { - const permission = await getPermission(req.user_id, webhook.guild_id); + if (webhook.guild_id) { + const permission = await getPermission(req.user_id, webhook.guild_id); - if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - return res.json({ - ...webhook, - url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, - }); - }, + return res.json({ + ...webhook, + url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, + }); + }, ); router.delete( - "/", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id } = req.params; + "/", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id } = req.params; - const webhook = await Webhook.findOneOrFail({ - where: { id: webhook_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + const webhook = await Webhook.findOneOrFail({ + where: { id: webhook_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - if (webhook.guild_id) { - const permission = await getPermission(req.user_id, webhook.guild_id); + if (webhook.guild_id) { + const permission = await getPermission(req.user_id, webhook.guild_id); - if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - const channel_id = webhook.channel_id; - await Webhook.delete({ id: webhook_id }); + const channel_id = webhook.channel_id; + await Webhook.delete({ id: webhook_id }); - await emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: webhook.guild_id, - }, - } as WebhooksUpdateEvent); + await emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: webhook.guild_id, + }, + } as WebhooksUpdateEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.patch( - "/", - route({ - requestBody: "WebhookUpdateSchema", - responses: { - 200: { - body: "WebhookCreateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id } = req.params; - const body = req.body as WebhookUpdateSchema; + "/", + route({ + requestBody: "WebhookUpdateSchema", + responses: { + 200: { + body: "WebhookCreateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id } = req.params; + const body = req.body as WebhookUpdateSchema; - const webhook = await Webhook.findOneOrFail({ - where: { id: webhook_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + const webhook = await Webhook.findOneOrFail({ + where: { id: webhook_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - if (webhook.guild_id) { - const permission = await getPermission(req.user_id, webhook.guild_id); + if (webhook.guild_id) { + const permission = await getPermission(req.user_id, webhook.guild_id); - if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - if (!body.name && !body.avatar && !body.channel_id) { - throw new HTTPError("Empty webhook updates are not allowed", 50006); - } + if (!body.name && !body.avatar && !body.channel_id) { + throw new HTTPError("Empty webhook updates are not allowed", 50006); + } - if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string); + if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string); - if (body.name) { - ValidateName(body.name); - } + if (body.name) { + ValidateName(body.name); + } - const channel_id = body.channel_id || webhook.channel_id; - webhook.assign(body); + const channel_id = body.channel_id || webhook.channel_id; + webhook.assign(body); - if (body.channel_id) - webhook.assign({ - channel: await Channel.findOneOrFail({ - where: { id: channel_id }, - }), - }); + if (body.channel_id) + webhook.assign({ + channel: await Channel.findOneOrFail({ + where: { id: channel_id }, + }), + }); - await Promise.all([ - webhook.save(), - emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: webhook.guild_id, - }, - } as WebhooksUpdateEvent), - ]); + await Promise.all([ + webhook.save(), + emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: webhook.guild_id, + }, + } as WebhooksUpdateEvent), + ]); - res.json(webhook); - }, + res.json(webhook); + }, ); export default router; diff --git a/src/api/start.ts b/src/api/start.ts index cd3373b..96c0c34 100644 --- a/src/api/start.ts +++ b/src/api/start.ts @@ -28,30 +28,30 @@ import os from "os"; let cores = 1; try { - cores = Number(process.env.THREADS) || os.cpus().length; + cores = Number(process.env.THREADS) || os.cpus().length; } catch { - console.log("[API] Failed to get thread count! Using 1..."); + console.log("[API] Failed to get thread count! Using 1..."); } if (cluster.isPrimary && process.env.NODE_ENV == "production") { - console.log(`Primary PID: ${process.pid}`); + console.log(`Primary PID: ${process.pid}`); - // Fork workers. - for (let i = 0; i < cores; i++) { - cluster.fork(); - } + // Fork workers. + for (let i = 0; i < cores; i++) { + cluster.fork(); + } - cluster.on("exit", (worker) => { - console.log(`Worker ${worker.process.pid} died, restarting worker`); - cluster.fork(); - }); + cluster.on("exit", (worker) => { + console.log(`Worker ${worker.process.pid} died, restarting worker`); + cluster.fork(); + }); } else { - const port = Number(process.env.PORT) || 3001; + const port = Number(process.env.PORT) || 3001; - const server = new SpacebarServer({ port }); - server.start().catch(console.error); + const server = new SpacebarServer({ port }); + server.start().catch(console.error); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - global.server = server; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + global.server = server; } diff --git a/src/api/util/handlers/Instance.ts b/src/api/util/handlers/Instance.ts index 86dda74..e71c3ed 100644 --- a/src/api/util/handlers/Instance.ts +++ b/src/api/util/handlers/Instance.ts @@ -21,34 +21,34 @@ import { setInterval } from "timers"; export async function initInstance() { - // TODO: clean up database and delete tombstone data - // TODO: set first user as instance administrator/or generate one if none exists and output it in the terminal + // TODO: clean up database and delete tombstone data + // TODO: set first user as instance administrator/or generate one if none exists and output it in the terminal - // create default guild and add it to auto join - // TODO: check if any current user is not part of autoJoinGuilds - // const { autoJoin } = Config.get().guild; + // create default guild and add it to auto join + // TODO: check if any current user is not part of autoJoinGuilds + // const { autoJoin } = Config.get().guild; - // if (autoJoin.enabled && !autoJoin.guilds?.length) { - // const guild = await Guild.findOne({ where: {}, select: ["id"] }); - // if (guild) { - // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); - // } - // } + // if (autoJoin.enabled && !autoJoin.guilds?.length) { + // const guild = await Guild.findOne({ where: {}, select: ["id"] }); + // if (guild) { + // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); + // } + // } - // TODO: do no clear sessions for instance cluster - // await Session.clear(); // This is now used as part of authentication... - // ... but we can still expire temporary sessions for legacy tokens - setInterval( - async () => { - for await (const session of await Session.createQueryBuilder("session").where("last_seen = '1970/01/01'").select().stream()) { - // session object has all fields prefixed with `session_`... thanks typeorm - if (TimeSpan.fromDates((session.session_created_at as Date).getTime(), new Date().getTime()).totalHours > 1) { - console.log(`[API/Instance.ts] Deleting unused session ${session.session_session_id} created at ${session.session_created_at}`); - await Session.delete({ session_id: session.session_session_id }); - } - } - }, - 1000 * 60 * 5, - ); - // await Session.delete({ session_id: Like("TEMP_%") }); + // TODO: do no clear sessions for instance cluster + // await Session.clear(); // This is now used as part of authentication... + // ... but we can still expire temporary sessions for legacy tokens + setInterval( + async () => { + for await (const session of await Session.createQueryBuilder("session").where("last_seen = '1970/01/01'").select().stream()) { + // session object has all fields prefixed with `session_`... thanks typeorm + if (TimeSpan.fromDates((session.session_created_at as Date).getTime(), new Date().getTime()).totalHours > 1) { + console.log(`[API/Instance.ts] Deleting unused session ${session.session_session_id} created at ${session.session_created_at}`); + await Session.delete({ session_id: session.session_session_id }); + } + } + }, + 1000 * 60 * 5, + ); + // await Session.delete({ session_id: Like("TEMP_%") }); } diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts index 06a3e08..65583f5 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts @@ -18,36 +18,36 @@ import { EmbedHandlers } from "@spacebar/api"; import { - Application, - Attachment, - Channel, - Config, - EmbedCache, - emitEvent, - EVERYONE_MENTION, - getPermission, - getRights, - Guild, - HERE_MENTION, - Message, - MessageCreateEvent, - MessageUpdateEvent, - Role, - ROLE_MENTION, - Sticker, - User, - //CHANNEL_MENTION, - USER_MENTION, - Webhook, - handleFile, - Permissions, - normalizeUrl, - DiscordApiErrors, - CloudAttachment, - ReadState, - Member, - Session, - MessageFlags, + Application, + Attachment, + Channel, + Config, + EmbedCache, + emitEvent, + EVERYONE_MENTION, + getPermission, + getRights, + Guild, + HERE_MENTION, + Message, + MessageCreateEvent, + MessageUpdateEvent, + Role, + ROLE_MENTION, + Sticker, + User, + //CHANNEL_MENTION, + USER_MENTION, + Webhook, + handleFile, + Permissions, + normalizeUrl, + DiscordApiErrors, + CloudAttachment, + ReadState, + Member, + Session, + MessageFlags, } from "@spacebar/util"; import { HTTPError } from "lambert-server"; import { In, Or, Equal, IsNull } from "typeorm"; @@ -59,515 +59,515 @@ const LINK_REGEX = /?/g; export async function handleMessage(opts: MessageOptions): Promise { - const channel = await Channel.findOneOrFail({ - where: { id: opts.channel_id }, - relations: ["recipients"], - }); - if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404); + const channel = await Channel.findOneOrFail({ + where: { id: opts.channel_id }, + relations: ["recipients"], + }); + if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404); - let permission: undefined | Permissions; - const limit = channel.rate_limit_per_user; + let permission: undefined | Permissions; + const limit = channel.rate_limit_per_user; - if (limit) { - const lastMsgTime = (await Message.findOne({ where: { channel_id: channel.id, author_id: opts.author_id }, select: { timestamp: true }, order: { timestamp: "DESC" } })) - ?.timestamp; - if (lastMsgTime && Date.now() - limit * 1000 < +lastMsgTime) { - permission ||= await getPermission(opts.author_id, channel.guild_id, channel); - //FIXME MANAGE_MESSAGES and MANAGE_CHANNELS will need to be removed once they're gone as checks - if (!permission.has("MANAGE_MESSAGES") && !permission.has("MANAGE_CHANNELS") && !permission.has("BYPASS_SLOWMODE")) { - throw DiscordApiErrors.SLOWMODE_RATE_LIMIT; - } - } - } + if (limit) { + const lastMsgTime = (await Message.findOne({ where: { channel_id: channel.id, author_id: opts.author_id }, select: { timestamp: true }, order: { timestamp: "DESC" } })) + ?.timestamp; + if (lastMsgTime && Date.now() - limit * 1000 < +lastMsgTime) { + permission ||= await getPermission(opts.author_id, channel.guild_id, channel); + //FIXME MANAGE_MESSAGES and MANAGE_CHANNELS will need to be removed once they're gone as checks + if (!permission.has("MANAGE_MESSAGES") && !permission.has("MANAGE_CHANNELS") && !permission.has("BYPASS_SLOWMODE")) { + throw DiscordApiErrors.SLOWMODE_RATE_LIMIT; + } + } + } - const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined; - // cloud attachments with indexes - const cloudAttachments = opts.attachments?.reduce( - (acc, att, index) => { - if ("uploaded_filename" in att) { - acc.push({ attachment: att, index }); - } - return acc; - }, - [] as { attachment: MessageCreateCloudAttachment; index: number }[], - ); + const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined; + // cloud attachments with indexes + const cloudAttachments = opts.attachments?.reduce( + (acc, att, index) => { + if ("uploaded_filename" in att) { + acc.push({ attachment: att, index }); + } + return acc; + }, + [] as { attachment: MessageCreateCloudAttachment; index: number }[], + ); - const message = Message.create({ - ...opts, - poll: opts.poll, - sticker_items: stickers, - guild_id: channel.guild_id, - channel_id: opts.channel_id, - attachments: opts.attachments || [], - embeds: opts.embeds || [], - reactions: opts.reactions || [], - type: opts.type ?? 0, - mentions: [], - components: opts.components ?? undefined, // Fix Discord-Go? - }); - const ephermal = (message.flags & (1 << 6)) !== 0; + const message = Message.create({ + ...opts, + poll: opts.poll, + sticker_items: stickers, + guild_id: channel.guild_id, + channel_id: opts.channel_id, + attachments: opts.attachments || [], + embeds: opts.embeds || [], + reactions: opts.reactions || [], + type: opts.type ?? 0, + mentions: [], + components: opts.components ?? undefined, // Fix Discord-Go? + }); + const ephermal = (message.flags & (1 << 6)) !== 0; - if (cloudAttachments && cloudAttachments.length > 0) { - console.log("[Message] Processing attachments for message", message.id, ":", message.attachments); - const uploadedAttachments = await Promise.all( - cloudAttachments.map(async (att) => { - const cAtt = att.attachment; - const attEnt = await CloudAttachment.findOneOrFail({ - where: { - uploadFilename: cAtt.uploaded_filename, - }, - }); + if (cloudAttachments && cloudAttachments.length > 0) { + console.log("[Message] Processing attachments for message", message.id, ":", message.attachments); + const uploadedAttachments = await Promise.all( + cloudAttachments.map(async (att) => { + const cAtt = att.attachment; + const attEnt = await CloudAttachment.findOneOrFail({ + where: { + uploadFilename: cAtt.uploaded_filename, + }, + }); - const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, { - method: "POST", - headers: { - signature: Config.get().security.requestSignature || "", - }, - }); + const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, { + method: "POST", + headers: { + signature: Config.get().security.requestSignature || "", + }, + }); - if (!cloneResponse.ok) { - console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`); - throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500); - } + if (!cloneResponse.ok) { + console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`); + throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500); + } - const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string }; + const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string }; - const realAtt = Attachment.create({ - filename: attEnt.userFilename, - url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, - proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, - size: attEnt.size, - height: attEnt.height, - width: attEnt.width, - content_type: attEnt.contentType || attEnt.userOriginalContentType, - }); - await realAtt.save(); - return { attachment: realAtt, index: att.index }; - }), - ); - console.log("[Message] Processed attachments for message", message.id, ":", message.attachments); + const realAtt = Attachment.create({ + filename: attEnt.userFilename, + url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, + proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, + size: attEnt.size, + height: attEnt.height, + width: attEnt.width, + content_type: attEnt.contentType || attEnt.userOriginalContentType, + }); + await realAtt.save(); + return { attachment: realAtt, index: att.index }; + }), + ); + console.log("[Message] Processed attachments for message", message.id, ":", message.attachments); - for (const att of uploadedAttachments) { - message.attachments![att.index] = att.attachment; - } - } - // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments); + for (const att of uploadedAttachments) { + message.attachments![att.index] = att.attachment; + } + } + // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments); - if (message.content && message.content.length > Config.get().limits.message.maxCharacters) { - throw new HTTPError("Content length over max character limit"); - } + if (message.content && message.content.length > Config.get().limits.message.maxCharacters) { + throw new HTTPError("Content length over max character limit"); + } - if (opts.author_id) { - message.author = await User.getPublicUser(opts.author_id); - const rights = await getRights(opts.author_id); - rights.hasThrow("SEND_MESSAGES"); - } - if (opts.application_id) { - message.application = await Application.findOneOrFail({ - where: { id: opts.application_id }, - }); - } + if (opts.author_id) { + message.author = await User.getPublicUser(opts.author_id); + const rights = await getRights(opts.author_id); + rights.hasThrow("SEND_MESSAGES"); + } + if (opts.application_id) { + message.application = await Application.findOneOrFail({ + where: { id: opts.application_id }, + }); + } - if (opts.webhook_id) { - message.webhook = await Webhook.findOneOrFail({ - where: { id: opts.webhook_id }, - }); + if (opts.webhook_id) { + message.webhook = await Webhook.findOneOrFail({ + where: { id: opts.webhook_id }, + }); - message.author = - (await User.findOne({ - where: { id: opts.webhook_id }, - })) || undefined; + message.author = + (await User.findOne({ + where: { id: opts.webhook_id }, + })) || undefined; - if (!message.author) { - message.author = User.create({ - id: opts.webhook_id, - username: message.webhook.name, - discriminator: "0000", - avatar: message.webhook.avatar, - public_flags: 0, - premium: false, - premium_type: 0, - bot: true, - created_at: new Date(), - verified: true, - rights: "0", - data: { - valid_tokens_since: new Date(), - }, - }); + if (!message.author) { + message.author = User.create({ + id: opts.webhook_id, + username: message.webhook.name, + discriminator: "0000", + avatar: message.webhook.avatar, + public_flags: 0, + premium: false, + premium_type: 0, + bot: true, + created_at: new Date(), + verified: true, + rights: "0", + data: { + valid_tokens_since: new Date(), + }, + }); - await message.author.save(); - } + await message.author.save(); + } - if (opts.username) { - message.username = opts.username; - message.author.username = message.username; - } - if (opts.avatar_url) { - const avatarData = await fetch(opts.avatar_url); - const base64 = await avatarData.arrayBuffer().then((x) => Buffer.from(x).toString("base64")); + if (opts.username) { + message.username = opts.username; + message.author.username = message.username; + } + if (opts.avatar_url) { + const avatarData = await fetch(opts.avatar_url); + const base64 = await avatarData.arrayBuffer().then((x) => Buffer.from(x).toString("base64")); - const dataUri = "data:" + avatarData.headers.get("content-type") + ";base64," + base64; + const dataUri = "data:" + avatarData.headers.get("content-type") + ";base64," + base64; - message.avatar = await handleFile(`/avatars/${opts.webhook_id}`, dataUri as string); - message.author.avatar = message.avatar; - } - } else { - permission ||= await getPermission(opts.author_id, channel.guild_id, channel); - permission.hasThrow("SEND_MESSAGES"); - if (permission.cache.member) { - message.member = permission.cache.member; - } + message.avatar = await handleFile(`/avatars/${opts.webhook_id}`, dataUri as string); + message.author.avatar = message.avatar; + } + } else { + permission ||= await getPermission(opts.author_id, channel.guild_id, channel); + permission.hasThrow("SEND_MESSAGES"); + if (permission.cache.member) { + message.member = permission.cache.member; + } - if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES"); - if (opts.message_reference) { - permission.hasThrow("READ_MESSAGE_HISTORY"); - // code below has to be redone when we add custom message routing - if (message.guild_id !== null) { - const guild = await Guild.findOneOrFail({ - where: { id: channel.guild_id }, - }); - if (!opts.message_reference.guild_id) opts.message_reference.guild_id = channel.guild_id; - if (!opts.message_reference.channel_id) opts.message_reference.channel_id = opts.channel_id; + if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES"); + if (opts.message_reference) { + permission.hasThrow("READ_MESSAGE_HISTORY"); + // code below has to be redone when we add custom message routing + if (message.guild_id !== null) { + const guild = await Guild.findOneOrFail({ + where: { id: channel.guild_id }, + }); + if (!opts.message_reference.guild_id) opts.message_reference.guild_id = channel.guild_id; + if (!opts.message_reference.channel_id) opts.message_reference.channel_id = opts.channel_id; - if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) { - if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild"); - if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); - } + if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) { + if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild"); + if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); + } - message.message_reference = opts.message_reference; - message.referenced_message = await Message.findOneOrFail({ - where: { - id: opts.message_reference.message_id, - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], - }); + message.message_reference = opts.message_reference; + message.referenced_message = await Message.findOneOrFail({ + where: { + id: opts.message_reference.message_id, + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], + }); - if (message.referenced_message.channel_id && message.referenced_message.channel_id !== opts.message_reference.channel_id) - throw new HTTPError("Referenced message not found in the specified channel", 404); - if (message.referenced_message.guild_id && message.referenced_message.guild_id !== opts.message_reference.guild_id) - throw new HTTPError("Referenced message not found in the specified channel", 404); - } - /** Q: should be checked if the referenced message exists? ANSWER: NO + if (message.referenced_message.channel_id && message.referenced_message.channel_id !== opts.message_reference.channel_id) + throw new HTTPError("Referenced message not found in the specified channel", 404); + if (message.referenced_message.guild_id && message.referenced_message.guild_id !== opts.message_reference.guild_id) + throw new HTTPError("Referenced message not found in the specified channel", 404); + } + /** Q: should be checked if the referenced message exists? ANSWER: NO otherwise backfilling won't work **/ - message.type = MessageType.REPLY; - } - } + message.type = MessageType.REPLY; + } + } - // TODO: stickers/activity - if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length && !opts.poll && !opts.components?.length) { - console.log("[Message] Rejecting empty message:", opts, message); - throw new HTTPError("Empty messages are not allowed", 50006); - } + // TODO: stickers/activity + if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length && !opts.poll && !opts.components?.length) { + console.log("[Message] Rejecting empty message:", opts, message); + throw new HTTPError("Empty messages are not allowed", 50006); + } - let content = opts.content; + let content = opts.content; - // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. - //const mention_channel_ids = [] as string[]; - const mention_role_ids = [] as string[]; - const mention_user_ids = [] as string[]; - let mention_everyone = false; + // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. + //const mention_channel_ids = [] as string[]; + const mention_role_ids = [] as string[]; + const mention_user_ids = [] as string[]; + let mention_everyone = false; - if (content) { - // TODO: explicit-only mentions - message.content = content.trim(); - content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks - // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. - /*for (const [, mention] of content.matchAll(CHANNEL_MENTION)) { + if (content) { + // TODO: explicit-only mentions + message.content = content.trim(); + content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks + // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. + /*for (const [, mention] of content.matchAll(CHANNEL_MENTION)) { if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention); }*/ - for (const [, mention] of content.matchAll(USER_MENTION)) { - if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention); - } + for (const [, mention] of content.matchAll(USER_MENTION)) { + if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention); + } - await Promise.all( - Array.from(content.matchAll(ROLE_MENTION)).map(async ([, mention]) => { - const role = await Role.findOneOrFail({ - where: { id: mention, guild_id: channel.guild_id }, - }); - if (role.mentionable || opts.webhook_id || permission?.has("MANAGE_ROLES")) { - mention_role_ids.push(mention); - } - }), - ); + await Promise.all( + Array.from(content.matchAll(ROLE_MENTION)).map(async ([, mention]) => { + const role = await Role.findOneOrFail({ + where: { id: mention, guild_id: channel.guild_id }, + }); + if (role.mentionable || opts.webhook_id || permission?.has("MANAGE_ROLES")) { + mention_role_ids.push(mention); + } + }), + ); - if (opts.webhook_id || permission?.has("MENTION_EVERYONE")) { - mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION); - } - } + if (opts.webhook_id || permission?.has("MENTION_EVERYONE")) { + mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION); + } + } - if (message.message_reference?.message_id) { - const referencedMessage = await Message.findOne({ - where: { - id: message.message_reference.message_id, - channel_id: message.channel_id, - }, - }); - if (referencedMessage && referencedMessage.author_id !== message.author_id) { - message.mentions.push( - User.create({ - id: referencedMessage.author_id, - }), - ); - } - } + if (message.message_reference?.message_id) { + const referencedMessage = await Message.findOne({ + where: { + id: message.message_reference.message_id, + channel_id: message.channel_id, + }, + }); + if (referencedMessage && referencedMessage.author_id !== message.author_id) { + message.mentions.push( + User.create({ + id: referencedMessage.author_id, + }), + ); + } + } - // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. - /*message.mention_channels = mention_channel_ids.map((x) => + // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. + /*message.mention_channels = mention_channel_ids.map((x) => Channel.create({ id: x }), );*/ - message.mention_roles = ( - await Promise.all( - mention_role_ids.map((x) => { - return Role.findOne({ where: { id: x } }); - }), - ) - ).filter((role) => role !== null); + message.mention_roles = ( + await Promise.all( + mention_role_ids.map((x) => { + return Role.findOne({ where: { id: x } }); + }), + ) + ).filter((role) => role !== null); - message.mentions = [ - ...message.mentions, - ...( - await Promise.all( - mention_user_ids.map((x) => { - return User.findOne({ where: { id: x } }); - }), - ) - ).filter((user) => user !== null), - ]; + message.mentions = [ + ...message.mentions, + ...( + await Promise.all( + mention_user_ids.map((x) => { + return User.findOne({ where: { id: x } }); + }), + ) + ).filter((user) => user !== null), + ]; - message.mention_everyone = mention_everyone; - async function fillInMissingIDs(ids: string[]) { - const states = await ReadState.findBy({ - user_id: Or(...ids.map((id) => Equal(id))), - channel_id: channel.id, - }); - const users = new Set(ids); - states.forEach((state) => users.delete(state.user_id)); - if (!users.size) { - return; - } - return Promise.all( - [...users].map((user_id) => { - return ReadState.create({ user_id, channel_id: channel.id }).save(); - }), - ); - } - if (ephermal) { - const id = message.interaction_metadata?.user_id; - if (id) { - let pinged = mention_everyone || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM; - if (!pinged) pinged = !!message.mentions.find((user) => user.id === id); - if (!pinged) pinged = !!(await Member.find({ where: { id, roles: Or(...message.mention_roles.map(({ id }) => Equal(id))) } })); - if (pinged) { - //stuff - } - } - } else if ((!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) { - if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) { - if (channel.recipients) { - await fillInMissingIDs(channel.recipients.map(({ user_id }) => user_id)); - } - } else { - console.log(channel.guild_id); - await fillInMissingIDs((await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id)); - } - const repository = ReadState.getRepository(); - const condition = { channel_id: channel.id }; - await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 }); - await repository.increment(condition, "mention_count", 1); - } else { - const users = new Set([ - ...(message.mention_roles.length - ? await Member.find({ - where: [ - ...message.mention_roles.map((role) => { - return { roles: { id: role.id } }; - }), - ], - }) - : [] - ).map((member) => member.id), - ...message.mentions.map((user) => user.id), - ]); - if (!!message.content?.match(HERE_MENTION) && permission?.has("MENTION_EVERYONE")) { - const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id); - (await Session.find({ where: { user_id: Or(...ids.map((id) => Equal(id))) } })).forEach(({ user_id }) => users.add(user_id)); - } - if (users.size) { - const repository = ReadState.getRepository(); - const condition = { user_id: Or(...[...users].map((id) => Equal(id))), channel_id: channel.id }; + message.mention_everyone = mention_everyone; + async function fillInMissingIDs(ids: string[]) { + const states = await ReadState.findBy({ + user_id: Or(...ids.map((id) => Equal(id))), + channel_id: channel.id, + }); + const users = new Set(ids); + states.forEach((state) => users.delete(state.user_id)); + if (!users.size) { + return; + } + return Promise.all( + [...users].map((user_id) => { + return ReadState.create({ user_id, channel_id: channel.id }).save(); + }), + ); + } + if (ephermal) { + const id = message.interaction_metadata?.user_id; + if (id) { + let pinged = mention_everyone || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM; + if (!pinged) pinged = !!message.mentions.find((user) => user.id === id); + if (!pinged) pinged = !!(await Member.find({ where: { id, roles: Or(...message.mention_roles.map(({ id }) => Equal(id))) } })); + if (pinged) { + //stuff + } + } + } else if ((!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) { + if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) { + if (channel.recipients) { + await fillInMissingIDs(channel.recipients.map(({ user_id }) => user_id)); + } + } else { + console.log(channel.guild_id); + await fillInMissingIDs((await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id)); + } + const repository = ReadState.getRepository(); + const condition = { channel_id: channel.id }; + await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 }); + await repository.increment(condition, "mention_count", 1); + } else { + const users = new Set([ + ...(message.mention_roles.length + ? await Member.find({ + where: [ + ...message.mention_roles.map((role) => { + return { roles: { id: role.id } }; + }), + ], + }) + : [] + ).map((member) => member.id), + ...message.mentions.map((user) => user.id), + ]); + if (!!message.content?.match(HERE_MENTION) && permission?.has("MENTION_EVERYONE")) { + const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id); + (await Session.find({ where: { user_id: Or(...ids.map((id) => Equal(id))) } })).forEach(({ user_id }) => users.add(user_id)); + } + if (users.size) { + const repository = ReadState.getRepository(); + const condition = { user_id: Or(...[...users].map((id) => Equal(id))), channel_id: channel.id }; - await fillInMissingIDs([...users]); + await fillInMissingIDs([...users]); - await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 }); - await repository.increment(condition, "mention_count", 1); - } - } + await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 }); + await repository.increment(condition, "mention_count", 1); + } + } - // TODO: check and put it all in the body + // TODO: check and put it all in the body - return message; + return message; } // TODO: cache link result in db export async function postHandleMessage(message: Message) { - const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown + const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown - const linkMatches = content?.match(LINK_REGEX) || []; + const linkMatches = content?.match(LINK_REGEX) || []; - const data = { ...message }; + const data = { ...message }; - const currentNormalizedUrls = new Set(); - for (const link of linkMatches) { - // Don't process links in <> - if (link.startsWith("<") && link.endsWith(">")) { - continue; - } - try { - const normalized = normalizeUrl(link); - currentNormalizedUrls.add(normalized); - } catch (e) { - continue; - } - } + const currentNormalizedUrls = new Set(); + for (const link of linkMatches) { + // Don't process links in <> + if (link.startsWith("<") && link.endsWith(">")) { + continue; + } + try { + const normalized = normalizeUrl(link); + currentNormalizedUrls.add(normalized); + } catch (e) { + continue; + } + } - data.embeds.forEach((embed) => { - if (!embed.type) { - embed.type = EmbedType.rich; - } - }); - // Filter out embeds that could be links, start from scratch - data.embeds = data.embeds.filter((embed) => embed.type === "rich"); + data.embeds.forEach((embed) => { + if (!embed.type) { + embed.type = EmbedType.rich; + } + }); + // Filter out embeds that could be links, start from scratch + data.embeds = data.embeds.filter((embed) => embed.type === "rich"); - const seenNormalizedUrls = new Set(); - const uniqueLinks: string[] = []; + const seenNormalizedUrls = new Set(); + const uniqueLinks: string[] = []; - for (const link of linkMatches.slice(0, 20)) { - // embed max 20 links - TODO: make this configurable with instance policies - // Don't embed links in <> - if (link.startsWith("<") && link.endsWith(">")) continue; + for (const link of linkMatches.slice(0, 20)) { + // embed max 20 links - TODO: make this configurable with instance policies + // Don't embed links in <> + if (link.startsWith("<") && link.endsWith(">")) continue; - try { - const normalized = normalizeUrl(link); + try { + const normalized = normalizeUrl(link); - if (!seenNormalizedUrls.has(normalized)) { - seenNormalizedUrls.add(normalized); - uniqueLinks.push(link); - } - } catch (e) { - // Invalid URL, skip - continue; - } - } + if (!seenNormalizedUrls.has(normalized)) { + seenNormalizedUrls.add(normalized); + uniqueLinks.push(link); + } + } catch (e) { + // Invalid URL, skip + continue; + } + } - if (uniqueLinks.length === 0) { - // No valid unique links found, update message to remove old embeds - data.embeds = data.embeds.filter((embed) => embed.type === "rich"); - const author = data.author?.toPublicUser(); - const event = { - event: "MESSAGE_UPDATE", - channel_id: message.channel_id, - data: { - ...data, - author, - }, - } as MessageUpdateEvent; - await Promise.all([emitEvent(event), Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })]); - return; - } + if (uniqueLinks.length === 0) { + // No valid unique links found, update message to remove old embeds + data.embeds = data.embeds.filter((embed) => embed.type === "rich"); + const author = data.author?.toPublicUser(); + const event = { + event: "MESSAGE_UPDATE", + channel_id: message.channel_id, + data: { + ...data, + author, + }, + } as MessageUpdateEvent; + await Promise.all([emitEvent(event), Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })]); + return; + } - const cachePromises = []; + const cachePromises = []; - for (const link of uniqueLinks) { - let url: URL; - try { - url = new URL(link); - } catch (e) { - // Skip invalid URLs - continue; - } + for (const link of uniqueLinks) { + let url: URL; + try { + url = new URL(link); + } catch (e) { + // Skip invalid URLs + continue; + } - const normalizedUrl = normalizeUrl(link); + const normalizedUrl = normalizeUrl(link); - // Check cache using normalized URL - const cached = await EmbedCache.findOne({ - where: { url: normalizedUrl }, - }); + // Check cache using normalized URL + const cached = await EmbedCache.findOne({ + where: { url: normalizedUrl }, + }); - if (cached) { - data.embeds.push(cached.embed); - continue; - } + if (cached) { + data.embeds.push(cached.embed); + continue; + } - // bit gross, but whatever! - const endpointPublic = Config.get().cdn.endpointPublic; // lol - const handler = url.hostname === new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"]; + // bit gross, but whatever! + const endpointPublic = Config.get().cdn.endpointPublic; // lol + const handler = url.hostname === new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"]; - try { - let res = await handler(url); - if (!res) continue; - // tried to use shorthand but types didn't like me L - if (!Array.isArray(res)) res = [res]; + try { + let res = await handler(url); + if (!res) continue; + // tried to use shorthand but types didn't like me L + if (!Array.isArray(res)) res = [res]; - for (const embed of res) { - // Cache with normalized URL - const cache = EmbedCache.create({ - url: normalizedUrl, - embed: embed, - }); - cachePromises.push(cache.save()); - data.embeds.push(embed); - } - } catch (e) { - console.error(`[Embeds] Error while generating embed for ${link}`, e); - } - } + for (const embed of res) { + // Cache with normalized URL + const cache = EmbedCache.create({ + url: normalizedUrl, + embed: embed, + }); + cachePromises.push(cache.save()); + data.embeds.push(embed); + } + } catch (e) { + console.error(`[Embeds] Error while generating embed for ${link}`, e); + } + } - await Promise.all([ - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id: message.channel_id, - data, - } as MessageUpdateEvent), - Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds }), - ...cachePromises, - ]); + await Promise.all([ + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id: message.channel_id, + data, + } as MessageUpdateEvent), + Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds }), + ...cachePromises, + ]); } export async function sendMessage(opts: MessageOptions) { - const message = await handleMessage({ ...opts, timestamp: new Date() }); + const message = await handleMessage({ ...opts, timestamp: new Date() }); - const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0; - await Promise.all([ - Message.insert(message), - emitEvent({ - event: "MESSAGE_CREATE", - ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }), - data: message.toJSON(), - } as MessageCreateEvent), - ]); + const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0; + await Promise.all([ + Message.insert(message), + emitEvent({ + event: "MESSAGE_CREATE", + ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }), + data: message.toJSON(), + } as MessageCreateEvent), + ]); - // no await as it should catch error non-blockingly - postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); + // no await as it should catch error non-blockingly + postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - return message; + return message; } interface MessageOptions extends MessageCreateSchema { - id?: string; - type?: MessageType; - pinned?: boolean; - author_id?: string; - webhook_id?: string; - application_id?: string; - embeds?: Embed[]; - reactions?: Reaction[]; - channel_id?: string; - attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this? - edited_timestamp?: Date; - timestamp?: Date; - username?: string; - avatar_url?: string; + id?: string; + type?: MessageType; + pinned?: boolean; + author_id?: string; + webhook_id?: string; + application_id?: string; + embeds?: Embed[]; + reactions?: Reaction[]; + channel_id?: string; + attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this? + edited_timestamp?: Date; + timestamp?: Date; + username?: string; + avatar_url?: string; } diff --git a/src/api/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts index e34b679..409338f 100644 --- a/src/api/util/handlers/Voice.ts +++ b/src/api/util/handlers/Voice.ts @@ -20,31 +20,31 @@ import { distanceBetweenLocations } from "../utility/ipAddress"; export async function getVoiceRegions(ipAddress: string, vip: boolean) { - const regions = Config.get().regions; - const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip)); - let optimalId = regions.default; + const regions = Config.get().regions; + const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip)); + let optimalId = regions.default; - if (!regions.useDefaultAsOptimal) { - const clientIpAnalysis = await IpDataClient.getIpInfo(ipAddress); + if (!regions.useDefaultAsOptimal) { + const clientIpAnalysis = await IpDataClient.getIpInfo(ipAddress); - let min = Number.POSITIVE_INFINITY; + let min = Number.POSITIVE_INFINITY; - for (const ar of availableRegions) { - //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call - const dist = distanceBetweenLocations(clientIpAnalysis!, ar.location || (await IpDataClient.getIpInfo(ar.endpoint))!); + for (const ar of availableRegions) { + //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call + const dist = distanceBetweenLocations(clientIpAnalysis!, ar.location || (await IpDataClient.getIpInfo(ar.endpoint))!); - if (dist < min) { - min = dist; - optimalId = ar.id; - } - } - } + if (dist < min) { + min = dist; + optimalId = ar.id; + } + } + } - return availableRegions.map((ar) => ({ - id: ar.id, - name: ar.name, - custom: ar.custom, - deprecated: ar.deprecated, - optimal: ar.id === optimalId, - })); + return availableRegions.map((ar) => ({ + id: ar.id, + name: ar.name, + custom: ar.custom, + deprecated: ar.deprecated, + optimal: ar.id === optimalId, + })); } diff --git a/src/api/util/handlers/Webhook.ts b/src/api/util/handlers/Webhook.ts index 54ef26e..cd0f2f6 100644 --- a/src/api/util/handlers/Webhook.ts +++ b/src/api/util/handlers/Webhook.ts @@ -6,118 +6,118 @@ import { WebhookExecuteSchema } from "@spacebar/schemas"; export const executeWebhook = async (req: Request, res: Response) => { - const body = req.body as WebhookExecuteSchema; + const body = req.body as WebhookExecuteSchema; - const { webhook_id, token } = req.params; + const { webhook_id, token } = req.params; - const webhook = await Webhook.findOne({ - where: { - id: webhook_id, - }, - relations: ["channel", "guild", "application"], - }); + const webhook = await Webhook.findOne({ + where: { + id: webhook_id, + }, + relations: ["channel", "guild", "application"], + }); - if (!webhook) { - throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } + if (!webhook) { + throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } - if (webhook.token !== token) { - throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; - } + if (webhook.token !== token) { + throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; + } - if (body.username) { - ValidateName(body.username); - } + if (body.username) { + ValidateName(body.username); + } - // ensure one of content, embeds, components, or file is present - if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) { - throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE; - } + // ensure one of content, embeds, components, or file is present + if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) { + throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE; + } - const wait = req.query.wait === "true"; + const wait = req.query.wait === "true"; - if (!wait) { - res.status(204).send(); - } + if (!wait) { + res.status(204).send(); + } - const attachments: Attachment[] = []; + const attachments: Attachment[] = []; - if (!webhook.channel.isWritable()) { - if (wait) { - throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400); - } else { - return; - } - } + if (!webhook.channel.isWritable()) { + if (wait) { + throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400); + } else { + return; + } + } - // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one? - const limits = Config.get().limits; - if (limits.absoluteRate.register.enabled) { - const count = await Message.count({ - where: { - channel_id: webhook.channel_id, - timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)), - }, - }); + // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one? + const limits = Config.get().limits; + if (limits.absoluteRate.register.enabled) { + const count = await Message.count({ + where: { + channel_id: webhook.channel_id, + timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)), + }, + }); - if (count >= limits.absoluteRate.sendMessage.limit) - if (wait) { - throw FieldErrors({ - channel_id: { - code: "TOO_MANY_MESSAGES", - message: req.t("common:toomany.MESSAGE"), - }, - }); - } else { - return; - } - } + if (count >= limits.absoluteRate.sendMessage.limit) + if (wait) { + throw FieldErrors({ + channel_id: { + code: "TOO_MANY_MESSAGES", + message: req.t("common:toomany.MESSAGE"), + }, + }); + } else { + return; + } + } - const files = (req.files as Express.Multer.File[]) ?? []; - for (const currFile of files) { - try { - const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile); - attachments.push(Attachment.create({ ...file, proxy_url: file.url })); - } catch (error) { - if (wait) res.status(400).json({ message: error?.toString() }); - return; - } - } + const files = (req.files as Express.Multer.File[]) ?? []; + for (const currFile of files) { + try { + const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile); + attachments.push(Attachment.create({ ...file, proxy_url: file.url })); + } catch (error) { + if (wait) res.status(400).json({ message: error?.toString() }); + return; + } + } - const embeds = body.embeds || []; - const message = await handleMessage({ - ...body, - username: body.username || webhook.name, - avatar_url: body.avatar_url || webhook.avatar, - type: 0, - pinned: false, - webhook_id: webhook.id, - application_id: webhook.application?.id, - embeds, - // TODO: Support thread_id/thread_name once threads are implemented - channel_id: webhook.channel_id, - attachments, - timestamp: new Date(), - }); + const embeds = body.embeds || []; + const message = await handleMessage({ + ...body, + username: body.username || webhook.name, + avatar_url: body.avatar_url || webhook.avatar, + type: 0, + pinned: false, + webhook_id: webhook.id, + application_id: webhook.application?.id, + embeds, + // TODO: Support thread_id/thread_name once threads are implemented + channel_id: webhook.channel_id, + attachments, + timestamp: new Date(), + }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore dont care2 - message.edited_timestamp = null; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore dont care2 + message.edited_timestamp = null; - webhook.channel.last_message_id = message.id; + webhook.channel.last_message_id = message.id; - await Promise.all([ - message.save(), - webhook.channel.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: webhook.channel_id, - data: message, - } as MessageCreateEvent), - ]); + await Promise.all([ + message.save(), + webhook.channel.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: webhook.channel_id, + data: message, + } as MessageCreateEvent), + ]); - // no await as it shouldnt block the message send function and silently catch error - postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - if (wait) res.json(message); - return; + // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); + if (wait) res.json(message); + return; }; diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts index bb38c53..7ae00a5 100644 --- a/src/api/util/handlers/route.ts +++ b/src/api/util/handlers/route.ts @@ -22,107 +22,107 @@ import { ajv } from "@spacebar/schemas"; const ignoredRequestSchemas = [ - // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix? - "SettingsProtoUpdateJsonSchema", + // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix? + "SettingsProtoUpdateJsonSchema", ]; declare global { - // TODO: fix this - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - interface Request { - permission?: Permissions; - } - } + // TODO: fix this + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + permission?: Permissions; + } + } } export type RouteResponse = { - status?: number; - body?: `${string}Response`; - headers?: Record; + status?: number; + body?: `${string}Response`; + headers?: Record; }; export interface RouteOptions { - permission?: PermissionResolvable; - right?: RightResolvable; - requestBody?: `${string}Schema`; // typescript interface name - responses?: { - [status: number]: { - // body?: `${string}Response`; - body?: string; - }; - }; - event?: EVENT | EVENT[]; - summary?: string; - description?: string; - query?: { - [key: string]: { - type: string; - required?: boolean; - description?: string; - values?: string[]; - }; - }; - deprecated?: boolean; - // test?: { - // response?: RouteResponse; - // body?: unknown; - // path?: string; - // event?: EVENT | EVENT[]; - // headers?: Record; - // }; + permission?: PermissionResolvable; + right?: RightResolvable; + requestBody?: `${string}Schema`; // typescript interface name + responses?: { + [status: number]: { + // body?: `${string}Response`; + body?: string; + }; + }; + event?: EVENT | EVENT[]; + summary?: string; + description?: string; + query?: { + [key: string]: { + type: string; + required?: boolean; + description?: string; + values?: string[]; + }; + }; + deprecated?: boolean; + // test?: { + // response?: RouteResponse; + // body?: unknown; + // path?: string; + // event?: EVENT | EVENT[]; + // headers?: Record; + // }; } export function route(opts: RouteOptions) { - let validate: AnyValidateFunction | undefined; - if (opts.requestBody) { - try { - validate = ajv.getSchema(opts.requestBody); - } catch (e) { - console.error("AJV getSchema failed!"); - throw e; - } + let validate: AnyValidateFunction | undefined; + if (opts.requestBody) { + try { + validate = ajv.getSchema(opts.requestBody); + } catch (e) { + console.error("AJV getSchema failed!"); + throw e; + } - if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`); - } + if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`); + } - return async (req: Request, res: Response, next: NextFunction) => { - if (opts.permission) { - req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); + return async (req: Request, res: Response, next: NextFunction) => { + if (opts.permission) { + req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); - const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission]; - requiredPerms.forEach((perm) => { - // bitfield comparison: check if user lacks certain permission - if (!req.permission!.has(new Permissions(perm))) { - throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string); - } - }); - } + const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission]; + requiredPerms.forEach((perm) => { + // bitfield comparison: check if user lacks certain permission + if (!req.permission!.has(new Permissions(perm))) { + throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string); + } + }); + } - if (opts.right) { - const required = new Rights(opts.right); - req.rights = await getRights(req.user_id); + if (opts.right) { + const required = new Rights(opts.right); + req.rights = await getRights(req.user_id); - if (!req.rights || !req.rights.has(required)) { - throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string); - } - } + if (!req.rights || !req.rights.has(required)) { + throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string); + } + } - if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) { - const valid = validate(req.body); - if (!valid) { - const fields: Record = {}; - validate.errors?.forEach( - (x) => - (fields[x.instancePath.slice(1)] = { - code: x.keyword, - message: x.message || "", - }), - ); - if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors); - throw FieldErrors(fields, validate.errors!); - } - } - next(); - }; + if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) { + const valid = validate(req.body); + if (!valid) { + const fields: Record = {}; + validate.errors?.forEach( + (x) => + (fields[x.instancePath.slice(1)] = { + code: x.keyword, + message: x.message || "", + }), + ); + if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors); + throw FieldErrors(fields, validate.errors!); + } + } + next(); + }; } diff --git a/src/api/util/utility/Base64.ts b/src/api/util/utility/Base64.ts index 7426d22..78a56f6 100644 --- a/src/api/util/utility/Base64.ts +++ b/src/api/util/utility/Base64.ts @@ -25,41 +25,41 @@ // 123 == 'z'.charCodeAt(0) + 1 const s2b = new Array(123); for (let i = 0; i < alphabet.length; i++) { - s2b[alphabet.charCodeAt(i)] = i; + s2b[alphabet.charCodeAt(i)] = i; } // number to base64 export const ntob = (n: number): string => { - if (n < 0) return `-${ntob(-n)}`; + if (n < 0) return `-${ntob(-n)}`; - let lo = n >>> 0; - let hi = (n / 4294967296) >>> 0; + let lo = n >>> 0; + let hi = (n / 4294967296) >>> 0; - let right = ""; - while (hi > 0) { - right = b2s[0x3f & lo] + right; - lo >>>= 6; - lo |= (0x3f & hi) << 26; - hi >>>= 6; - } + let right = ""; + while (hi > 0) { + right = b2s[0x3f & lo] + right; + lo >>>= 6; + lo |= (0x3f & hi) << 26; + hi >>>= 6; + } - let left = ""; - do { - left = b2s[0x3f & lo] + left; - lo >>>= 6; - } while (lo > 0); + let left = ""; + do { + left = b2s[0x3f & lo] + left; + lo >>>= 6; + } while (lo > 0); - return left + right; + return left + right; }; // base64 to number export const bton = (base64: string) => { - let number = 0; - const sign = base64.charAt(0) === "-" ? 1 : 0; + let number = 0; + const sign = base64.charAt(0) === "-" ? 1 : 0; - for (let i = sign; i < base64.length; i++) { - number = number * 64 + s2b[base64.charCodeAt(i)]; - } + for (let i = sign; i < base64.length; i++) { + number = number * 64 + s2b[base64.charCodeAt(i)]; + } - return sign ? -number : number; + return sign ? -number : number; }; diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts index 8bbd628..2e50c53 100644 --- a/src/api/util/utility/EmbedHandlers.ts +++ b/src/api/util/utility/EmbedHandlers.ts @@ -24,494 +24,494 @@ import probe from "probe-image-size"; export const DEFAULT_FETCH_OPTIONS: RequestInit = { - redirect: "follow", - headers: { - "user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)", - }, - // size: 1024 * 1024 * 5, // grabbed from config later - method: "GET", + redirect: "follow", + headers: { + "user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)", + }, + // size: 1024 * 1024 * 5, // grabbed from config later + method: "GET", }; const makeEmbedImage = (url: string | undefined, width: number | undefined, height: number | undefined): Required | undefined => { - if (!url || !width || !height) return undefined; - return { - url, - width, - height, - proxy_url: getProxyUrl(new URL(url), width, height), - }; + if (!url || !width || !height) return undefined; + return { + url, + width, + height, + proxy_url: getProxyUrl(new URL(url), width, height), + }; }; let hasWarnedAboutImagor = false; export const getProxyUrl = (url: URL, width: number, height: number): string => { - const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn; - const secret = Config.get().security.requestSignature; - width = Math.min(width || 500, resizeWidthMax || width); - height = Math.min(height || 500, resizeHeightMax || width); + const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn; + const secret = Config.get().security.requestSignature; + width = Math.min(width || 500, resizeWidthMax || width); + height = Math.min(height || 500, resizeHeightMax || width); - // Imagor - if (imagorServerUrl) { - const path = `${width}x${height}/${url.host}${url.pathname}`; + // Imagor + if (imagorServerUrl) { + const path = `${width}x${height}/${url.host}${url.pathname}`; - const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_"); + const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_"); - return `${imagorServerUrl}/${hash}/${path}`; - } + return `${imagorServerUrl}/${hash}/${path}`; + } - if (!hasWarnedAboutImagor) { - hasWarnedAboutImagor = true; - console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/")); - } + if (!hasWarnedAboutImagor) { + hasWarnedAboutImagor = true; + console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/")); + } - return url.toString(); + return url.toString(); }; const getMeta = ($: cheerio.CheerioAPI, name: string): string | undefined => { - let elem = $(`meta[property="${name}"]`); - if (!elem.length) elem = $(`meta[name="${name}"]`); - const ret = elem.attr("content") || elem.text(); - return ret.trim().length == 0 ? undefined : ret; + let elem = $(`meta[property="${name}"]`); + if (!elem.length) elem = $(`meta[name="${name}"]`); + const ret = elem.attr("content") || elem.text(); + return ret.trim().length == 0 ? undefined : ret; }; const tryParseInt = (str: string | undefined) => { - if (!str) return undefined; - try { - return parseInt(str); - } catch (e) { - return undefined; - } + if (!str) return undefined; + try { + return parseInt(str); + } catch (e) { + return undefined; + } }; export const getMetaDescriptions = (text: string) => { - const $ = cheerio.load(text); + const $ = cheerio.load(text); - return { - type: getMeta($, "og:type"), - title: getMeta($, "og:title") || $("title").first().text(), - provider_name: getMeta($, "og:site_name"), - author: getMeta($, "article:author"), - description: getMeta($, "og:description") || getMeta($, "description"), - image: getMeta($, "og:image") || getMeta($, "twitter:image"), - image_fallback: $(`image`).attr("src"), - video_fallback: $(`video`).attr("src"), - width: tryParseInt(getMeta($, "og:image:width")), - height: tryParseInt(getMeta($, "og:image:height")), - url: getMeta($, "og:url"), - youtube_embed: getMeta($, "og:video:secure_url"), - site_name: getMeta($, "og:site_name"), + return { + type: getMeta($, "og:type"), + title: getMeta($, "og:title") || $("title").first().text(), + provider_name: getMeta($, "og:site_name"), + author: getMeta($, "article:author"), + description: getMeta($, "og:description") || getMeta($, "description"), + image: getMeta($, "og:image") || getMeta($, "twitter:image"), + image_fallback: $(`image`).attr("src"), + video_fallback: $(`video`).attr("src"), + width: tryParseInt(getMeta($, "og:image:width")), + height: tryParseInt(getMeta($, "og:image:height")), + url: getMeta($, "og:url"), + youtube_embed: getMeta($, "og:video:secure_url"), + site_name: getMeta($, "og:site_name"), - $, - }; + $, + }; }; const doFetch = async (url: URL) => { - try { - const res = await fetch(url, { - ...DEFAULT_FETCH_OPTIONS, - }); - if (res.headers.get("content-length")) { - const contentLength = parseInt(res.headers.get("content-length")!); - if (Config.get().limits.message.maxEmbedDownloadSize && contentLength > Config.get().limits.message.maxEmbedDownloadSize) { - return null; - } - } - return res; - } catch (e) { - return null; - } + try { + const res = await fetch(url, { + ...DEFAULT_FETCH_OPTIONS, + }); + if (res.headers.get("content-length")) { + const contentLength = parseInt(res.headers.get("content-length")!); + if (Config.get().limits.message.maxEmbedDownloadSize && contentLength > Config.get().limits.message.maxEmbedDownloadSize) { + return null; + } + } + return res; + } catch (e) { + return null; + } }; const genericImageHandler = async (url: URL): Promise => { - const type = await fetch(url, { - ...DEFAULT_FETCH_OPTIONS, - method: "HEAD", - }); + const type = await fetch(url, { + ...DEFAULT_FETCH_OPTIONS, + method: "HEAD", + }); - let image; + let image; - if (type.headers.get("content-type")?.indexOf("image") !== -1) { - const result = await probe(url.href); - image = makeEmbedImage(url.href, result.width, result.height); - } else if (type.headers.get("content-type")?.indexOf("video") !== -1) { - // TODO - return null; - } else { - // have to download the page, unfortunately - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); - image = makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height); - } + if (type.headers.get("content-type")?.indexOf("image") !== -1) { + const result = await probe(url.href); + image = makeEmbedImage(url.href, result.width, result.height); + } else if (type.headers.get("content-type")?.indexOf("video") !== -1) { + // TODO + return null; + } else { + // have to download the page, unfortunately + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); + image = makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height); + } - if (!image) return null; + if (!image) return null; - return { - url: url.href, - type: EmbedType.image, - thumbnail: image, - }; + return { + url: url.href, + type: EmbedType.image, + thumbnail: image, + }; }; export const EmbedHandlers: { - [key: string]: (url: URL) => Promise; + [key: string]: (url: URL) => Promise; } = { - // the url does not have a special handler - default: async (url: URL) => { - const type = await fetch(url, { - ...DEFAULT_FETCH_OPTIONS, - method: "HEAD", - }); - if (type.headers.get("content-type")?.indexOf("image") !== -1) return await genericImageHandler(url); + // the url does not have a special handler + default: async (url: URL) => { + const type = await fetch(url, { + ...DEFAULT_FETCH_OPTIONS, + method: "HEAD", + }); + if (type.headers.get("content-type")?.indexOf("image") !== -1) return await genericImageHandler(url); - const response = await doFetch(url); - if (!response) return null; + const response = await doFetch(url); + if (!response) return null; - const text = await response.text(); - const metas = getMetaDescriptions(text); + const text = await response.text(); + const metas = getMetaDescriptions(text); - // TODO: handle video + // TODO: handle video - if (!metas.image) metas.image = metas.image_fallback; + if (!metas.image) metas.image = metas.image_fallback; - if (metas.image && (!metas.width || !metas.height)) { - metas.image = new URL(metas.image, url).toString(); - const result = await probe(metas.image); - metas.width = result.width; - metas.height = result.height; - } + if (metas.image && (!metas.width || !metas.height)) { + metas.image = new URL(metas.image, url).toString(); + const result = await probe(metas.image); + metas.width = result.width; + metas.height = result.height; + } - if (!metas.image && (!metas.title || !metas.description)) { - // we don't have any content to display - return null; - } + if (!metas.image && (!metas.title || !metas.description)) { + // we don't have any content to display + return null; + } - let embedType = EmbedType.link; - if (metas.type == "article") embedType = EmbedType.article; - if (metas.type == "object") embedType = EmbedType.article; // github - if (metas.type == "rich") embedType = EmbedType.rich; + let embedType = EmbedType.link; + if (metas.type == "article") embedType = EmbedType.article; + if (metas.type == "object") embedType = EmbedType.article; // github + if (metas.type == "rich") embedType = EmbedType.rich; - return { - url: url.href, - type: embedType, - title: metas.title, - thumbnail: makeEmbedImage(metas.image, metas.width, metas.height), - description: metas.description, - provider: metas.site_name - ? { - name: metas.site_name, - url: url.origin, - } - : undefined, - }; - }, + return { + url: url.href, + type: embedType, + title: metas.title, + thumbnail: makeEmbedImage(metas.image, metas.width, metas.height), + description: metas.description, + provider: metas.site_name + ? { + name: metas.site_name, + url: url.origin, + } + : undefined, + }; + }, - "giphy.com": genericImageHandler, - "media4.giphy.com": genericImageHandler, - "tenor.com": genericImageHandler, - "c.tenor.com": genericImageHandler, - "media.tenor.com": genericImageHandler, + "giphy.com": genericImageHandler, + "media4.giphy.com": genericImageHandler, + "tenor.com": genericImageHandler, + "c.tenor.com": genericImageHandler, + "media.tenor.com": genericImageHandler, - "facebook.com": (url) => EmbedHandlers["www.facebook.com"](url), - "www.facebook.com": async (url: URL) => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + "facebook.com": (url) => EmbedHandlers["www.facebook.com"](url), + "www.facebook.com": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - return { - url: url.href, - type: EmbedType.link, - title: metas.title, - description: metas.description, - thumbnail: makeEmbedImage(metas.image, 640, 640), - color: 16777215, - }; - }, + return { + url: url.href, + type: EmbedType.link, + title: metas.title, + description: metas.description, + thumbnail: makeEmbedImage(metas.image, 640, 640), + color: 16777215, + }; + }, - "twitter.com": (url) => EmbedHandlers["www.twitter.com"](url), - "www.twitter.com": async (url: URL) => { - const token = Config.get().external.twitter; - if (!token) return null; + "twitter.com": (url) => EmbedHandlers["www.twitter.com"](url), + "www.twitter.com": async (url: URL) => { + const token = Config.get().external.twitter; + if (!token) return null; - if (!url.href.includes("/status/")) return null; // TODO; - const id = url.pathname.split("/")[3]; // super bad lol - if (!parseInt(id)) return null; - const endpointUrl = - `https://api.twitter.com/2/tweets/${id}` + - `?expansions=author_id,attachments.media_keys` + - `&media.fields=url,width,height` + - `&tweet.fields=created_at,public_metrics` + - `&user.fields=profile_image_url`; + if (!url.href.includes("/status/")) return null; // TODO; + const id = url.pathname.split("/")[3]; // super bad lol + if (!parseInt(id)) return null; + const endpointUrl = + `https://api.twitter.com/2/tweets/${id}` + + `?expansions=author_id,attachments.media_keys` + + `&media.fields=url,width,height` + + `&tweet.fields=created_at,public_metrics` + + `&user.fields=profile_image_url`; - const response = await fetch(endpointUrl, { - ...DEFAULT_FETCH_OPTIONS, - headers: { - authorization: `Bearer ${token}`, - }, - }); - const json = (await response.json()) as { - errors?: never[]; - includes: { - users: { - profile_image_url: string; - username: string; - name: string; - }[]; - media: { - type: string; - width: number; - height: number; - url: string; - }[]; - }; - data: { - text: string; - created_at: string; - public_metrics: { like_count: number; retweet_count: number }; - }; - }; - if (json.errors) return null; - const author = json.includes.users[0]; - const text = json.data.text; - const created_at = new Date(json.data.created_at); - const metrics = json.data.public_metrics; - const media = json.includes.media?.filter((x: { type: string }) => x.type == "photo"); + const response = await fetch(endpointUrl, { + ...DEFAULT_FETCH_OPTIONS, + headers: { + authorization: `Bearer ${token}`, + }, + }); + const json = (await response.json()) as { + errors?: never[]; + includes: { + users: { + profile_image_url: string; + username: string; + name: string; + }[]; + media: { + type: string; + width: number; + height: number; + url: string; + }[]; + }; + data: { + text: string; + created_at: string; + public_metrics: { like_count: number; retweet_count: number }; + }; + }; + if (json.errors) return null; + const author = json.includes.users[0]; + const text = json.data.text; + const created_at = new Date(json.data.created_at); + const metrics = json.data.public_metrics; + const media = json.includes.media?.filter((x: { type: string }) => x.type == "photo"); - const embed: Embed = { - type: EmbedType.rich, - url: `${url.origin}${url.pathname}`, - description: text, - author: { - url: `https://twitter.com/${author.username}`, - name: `${author.name} (@${author.username})`, - proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400), - icon_url: author.profile_image_url, - }, - timestamp: created_at, - fields: [ - { - inline: true, - name: "Likes", - value: metrics.like_count.toString(), - }, - { - inline: true, - name: "Retweet", - value: metrics.retweet_count.toString(), - }, - ], - color: 1942002, - footer: { - text: "Twitter", - proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192), - icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png", - }, - // Discord doesn't send this? - // provider: { - // name: "Twitter", - // url: "https://twitter.com" - // }, - }; + const embed: Embed = { + type: EmbedType.rich, + url: `${url.origin}${url.pathname}`, + description: text, + author: { + url: `https://twitter.com/${author.username}`, + name: `${author.name} (@${author.username})`, + proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400), + icon_url: author.profile_image_url, + }, + timestamp: created_at, + fields: [ + { + inline: true, + name: "Likes", + value: metrics.like_count.toString(), + }, + { + inline: true, + name: "Retweet", + value: metrics.retweet_count.toString(), + }, + ], + color: 1942002, + footer: { + text: "Twitter", + proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192), + icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png", + }, + // Discord doesn't send this? + // provider: { + // name: "Twitter", + // url: "https://twitter.com" + // }, + }; - if (media && media.length > 0) { - embed.image = { - width: media[0].width, - height: media[0].height, - url: media[0].url, - proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height), - }; - media.shift(); - } + if (media && media.length > 0) { + embed.image = { + width: media[0].width, + height: media[0].height, + url: media[0].url, + proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height), + }; + media.shift(); + } - return embed; + return embed; - // TODO: Client won't merge these into a single embed, for some reason. - // return [embed, ...media.map((x: any) => ({ - // // generate new embeds for each additional attachment - // type: EmbedType.rich, - // url: url.href, - // image: { - // width: x.width, - // height: x.height, - // url: x.url, - // proxy_url: getProxyUrl(new URL(x.url), x.width, x.height) - // } - // }))]; - }, + // TODO: Client won't merge these into a single embed, for some reason. + // return [embed, ...media.map((x: any) => ({ + // // generate new embeds for each additional attachment + // type: EmbedType.rich, + // url: url.href, + // image: { + // width: x.width, + // height: x.height, + // url: x.url, + // proxy_url: getProxyUrl(new URL(x.url), x.width, x.height) + // } + // }))]; + }, - "open.spotify.com": async (url: URL) => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + "open.spotify.com": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - return { - url: url.href, - type: EmbedType.link, - title: metas.title, - description: metas.description, - thumbnail: makeEmbedImage(metas.image, 640, 640), - provider: { - url: "https://spotify.com", - name: "Spotify", - }, - }; - }, + return { + url: url.href, + type: EmbedType.link, + title: metas.title, + description: metas.description, + thumbnail: makeEmbedImage(metas.image, 640, 640), + provider: { + url: "https://spotify.com", + name: "Spotify", + }, + }; + }, - // TODO: docs: Pixiv won't work without Imagor - "pixiv.net": (url) => EmbedHandlers["www.pixiv.net"](url), - "www.pixiv.net": async (url: URL) => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + // TODO: docs: Pixiv won't work without Imagor + "pixiv.net": (url) => EmbedHandlers["www.pixiv.net"](url), + "www.pixiv.net": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - if (!metas.image) return null; + if (!metas.image) return null; - return { - url: url.href, - type: EmbedType.image, - title: metas.title, - description: metas.description, - image: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height), - provider: { - url: "https://pixiv.net", - name: "Pixiv", - }, - }; - }, + return { + url: url.href, + type: EmbedType.image, + title: metas.title, + description: metas.description, + image: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height), + provider: { + url: "https://pixiv.net", + name: "Pixiv", + }, + }; + }, - "store.steampowered.com": async (url: URL) => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); - const numReviews = metas.$("#review_summary_num_reviews").val() as string | undefined; - const price = metas.$(".game_purchase_price.price").data("price-final") as number | undefined; - const releaseDate = metas.$(".release_date").find("div.date").text().trim(); - const isReleased = new Date(releaseDate) < new Date(); + "store.steampowered.com": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); + const numReviews = metas.$("#review_summary_num_reviews").val() as string | undefined; + const price = metas.$(".game_purchase_price.price").data("price-final") as number | undefined; + const releaseDate = metas.$(".release_date").find("div.date").text().trim(); + const isReleased = new Date(releaseDate) < new Date(); - const fields: Embed["fields"] = []; + const fields: Embed["fields"] = []; - if (numReviews) - fields.push({ - name: "Reviews", - value: numReviews, - inline: true, - }); + if (numReviews) + fields.push({ + name: "Reviews", + value: numReviews, + inline: true, + }); - if (price) - fields.push({ - name: "Price", - value: `$${price / 100}`, - inline: true, - }); + if (price) + fields.push({ + name: "Price", + value: `$${price / 100}`, + inline: true, + }); - // if the release date is in the past, it's already out - if (releaseDate && !isReleased) - fields.push({ - name: "Release Date", - value: releaseDate, - inline: true, - }); + // if the release date is in the past, it's already out + if (releaseDate && !isReleased) + fields.push({ + name: "Release Date", + value: releaseDate, + inline: true, + }); - return { - url: url.href, - type: EmbedType.rich, - title: metas.title, - description: metas.description, - image: { - // TODO: meant to be thumbnail. - // isn't this standard across all of steam? - width: 460, - height: 215, - url: metas.image, - proxy_url: metas.image ? getProxyUrl(new URL(metas.image), 460, 215) : undefined, - }, - provider: { - url: "https://store.steampowered.com", - name: "Steam", - }, - fields, - // TODO: Video - }; - }, + return { + url: url.href, + type: EmbedType.rich, + title: metas.title, + description: metas.description, + image: { + // TODO: meant to be thumbnail. + // isn't this standard across all of steam? + width: 460, + height: 215, + url: metas.image, + proxy_url: metas.image ? getProxyUrl(new URL(metas.image), 460, 215) : undefined, + }, + provider: { + url: "https://store.steampowered.com", + name: "Steam", + }, + fields, + // TODO: Video + }; + }, - "reddit.com": (url) => EmbedHandlers["www.reddit.com"](url), - "www.reddit.com": async (url: URL) => { - const res = await EmbedHandlers["default"](url); - return { - ...res, - color: 16777215, - provider: { - name: "reddit", - }, - }; - }, + "reddit.com": (url) => EmbedHandlers["www.reddit.com"](url), + "www.reddit.com": async (url: URL) => { + const res = await EmbedHandlers["default"](url); + return { + ...res, + color: 16777215, + provider: { + name: "reddit", + }, + }; + }, - "youtu.be": (url) => EmbedHandlers["www.youtube.com"](url), - "youtube.com": (url) => EmbedHandlers["www.youtube.com"](url), - "www.youtube.com": async (url: URL): Promise => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + "youtu.be": (url) => EmbedHandlers["www.youtube.com"](url), + "youtube.com": (url) => EmbedHandlers["www.youtube.com"](url), + "www.youtube.com": async (url: URL): Promise => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - return { - video: makeEmbedImage(metas.youtube_embed, metas.width, metas.height), - url: url.href, - type: metas.youtube_embed ? EmbedType.video : EmbedType.link, - title: metas.title, - thumbnail: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height), - provider: { - url: "https://www.youtube.com", - name: "YouTube", - }, - description: metas.description, - color: 16711680, - author: metas.author - ? { - name: metas.author, - // TODO: author channel url - } - : undefined, - }; - }, + return { + video: makeEmbedImage(metas.youtube_embed, metas.width, metas.height), + url: url.href, + type: metas.youtube_embed ? EmbedType.video : EmbedType.link, + title: metas.title, + thumbnail: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height), + provider: { + url: "https://www.youtube.com", + name: "YouTube", + }, + description: metas.description, + color: 16711680, + author: metas.author + ? { + name: metas.author, + // TODO: author channel url + } + : undefined, + }; + }, - "www.xkcd.com": (url) => EmbedHandlers["xkcd.com"](url), - "xkcd.com": async (url) => { - const response = await doFetch(url); - if (!response) return null; + "www.xkcd.com": (url) => EmbedHandlers["xkcd.com"](url), + "xkcd.com": async (url) => { + const response = await doFetch(url); + if (!response) return null; - const metas = getMetaDescriptions(await response.text()); - const hoverText = metas.$("#comic img").attr("title"); + const metas = getMetaDescriptions(await response.text()); + const hoverText = metas.$("#comic img").attr("title"); - if (!metas.image) return null; + if (!metas.image) return null; - const { width, height } = await probe(metas.image); + const { width, height } = await probe(metas.image); - return { - url: url.href, - type: EmbedType.rich, - title: `xkcd: ${metas.title}`, - image: makeEmbedImage(metas.image, width, height), - footer: hoverText - ? { - text: hoverText, - } - : undefined, - }; - }, + return { + url: url.href, + type: EmbedType.rich, + title: `xkcd: ${metas.title}`, + image: makeEmbedImage(metas.image, width, height), + footer: hoverText + ? { + text: hoverText, + } + : undefined, + }; + }, - // the url is an image from this instance - self: async (url: URL): Promise => { - const result = await probe(url.href); + // the url is an image from this instance + self: async (url: URL): Promise => { + const result = await probe(url.href); - return { - url: url.href, - type: EmbedType.image, - thumbnail: { - width: result.width, - height: result.height, - url: url.href, - proxy_url: url.href, - }, - }; - }, + return { + url: url.href, + type: EmbedType.image, + thumbnail: { + width: result.width, + height: result.height, + url: url.href, + proxy_url: url.href, + }, + }; + }, }; diff --git a/src/api/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts index 3850df5..0718a73 100644 --- a/src/api/util/utility/RandomInviteID.ts +++ b/src/api/util/utility/RandomInviteID.ts @@ -23,42 +23,42 @@ // And why is this even here? Just use cryto.randomBytes? export function randomString(length = 6) { - // Declare all characters - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + // Declare all characters + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - // Pick characers randomly - let str = ""; - for (let i = 0; i < length; i++) { - str += chars.charAt(Math.floor(crypto.randomInt(chars.length))); - } + // Pick characers randomly + let str = ""; + for (let i = 0; i < length; i++) { + str += chars.charAt(Math.floor(crypto.randomInt(chars.length))); + } - return str; + return str; } export function snowflakeBasedInvite() { - // Declare all characters - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - const base = BigInt(chars.length); - let snowflake = Snowflake.generateWorkerProcess(); + // Declare all characters + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const base = BigInt(chars.length); + let snowflake = Snowflake.generateWorkerProcess(); - // snowflakes hold ~10.75 characters worth of entropy; - // safe to generate a 8-char invite out of them - const str = ""; - for (let i = 0; i < 10; i++) { - str.concat(chars.charAt(Number(snowflake % base))); - snowflake = snowflake / base; - } + // snowflakes hold ~10.75 characters worth of entropy; + // safe to generate a 8-char invite out of them + const str = ""; + for (let i = 0; i < 10; i++) { + str.concat(chars.charAt(Number(snowflake % base))); + snowflake = snowflake / base; + } - return str.substr(3, 8).split("").reverse().join(""); + return str.substr(3, 8).split("").reverse().join(""); } export function randomUpperString(length: number = 10) { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - let result = ""; - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } + let result = ""; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } - return result; + return result; } diff --git a/src/api/util/utility/String.ts b/src/api/util/utility/String.ts index e5c1279..e33613a 100644 --- a/src/api/util/utility/String.ts +++ b/src/api/util/utility/String.ts @@ -21,18 +21,18 @@ import { FieldErrors, Random } from "@spacebar/util"; export function checkLength(str: string, min: number, max: number, key: string, req: Request) { - if (str.length < min || str.length > max) { - throw FieldErrors({ - [key]: { - code: "BASE_TYPE_BAD_LENGTH", - message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { - length: `${min} - ${max}`, - }), - }, - }); - } + if (str.length < min || str.length > max) { + throw FieldErrors({ + [key]: { + code: "BASE_TYPE_BAD_LENGTH", + message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { + length: `${min} - ${max}`, + }), + }, + }); + } } export function generateCode() { - return ntob(Date.now() + Random.nextInt(0, 10000)); + return ntob(Date.now() + Random.nextInt(0, 10000)); } diff --git a/src/api/util/utility/captcha.ts b/src/api/util/utility/captcha.ts index f6d4d0e..c4d8c9a 100644 --- a/src/api/util/utility/captcha.ts +++ b/src/api/util/utility/captcha.ts @@ -19,46 +19,46 @@ import { Config } from "@spacebar/util"; export interface hcaptchaResponse { - success: boolean; - challenge_ts: string; - hostname: string; - credit: boolean; - "error-codes": string[]; - score: number; // enterprise only - score_reason: string[]; // enterprise only + success: boolean; + challenge_ts: string; + hostname: string; + credit: boolean; + "error-codes": string[]; + score: number; // enterprise only + score_reason: string[]; // enterprise only } export interface recaptchaResponse { - success: boolean; - score: number; // between 0 - 1 - action: string; - challenge_ts: string; - hostname: string; - "error-codes"?: string[]; + success: boolean; + score: number; // between 0 - 1 + action: string; + challenge_ts: string; + hostname: string; + "error-codes"?: string[]; } const verifyEndpoints = { - hcaptcha: "https://hcaptcha.com/siteverify", - recaptcha: "https://www.google.com/recaptcha/api/siteverify", + hcaptcha: "https://hcaptcha.com/siteverify", + recaptcha: "https://www.google.com/recaptcha/api/siteverify", }; export async function verifyCaptcha(response: string, ip?: string) { - const { security } = Config.get(); - const { service, secret, sitekey } = security.captcha; + const { security } = Config.get(); + const { service, secret, sitekey } = security.captcha; - if (!service || !secret || !sitekey) throw new Error("CAPTCHA is not configured correctly. https://docs.spacebar.chat/setup/server/security/captcha/"); + if (!service || !secret || !sitekey) throw new Error("CAPTCHA is not configured correctly. https://docs.spacebar.chat/setup/server/security/captcha/"); - const res = await fetch(verifyEndpoints[service], { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: - `response=${encodeURIComponent(response)}` + - `&secret=${encodeURIComponent(secret)}` + - `&sitekey=${encodeURIComponent(sitekey)}` + - (ip ? `&remoteip=${encodeURIComponent(ip)}` : ""), - }); + const res = await fetch(verifyEndpoints[service], { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: + `response=${encodeURIComponent(response)}` + + `&secret=${encodeURIComponent(secret)}` + + `&sitekey=${encodeURIComponent(sitekey)}` + + (ip ? `&remoteip=${encodeURIComponent(ip)}` : ""), + }); - return (await res.json()) as hcaptchaResponse | recaptchaResponse; + return (await res.json()) as hcaptchaResponse | recaptchaResponse; } diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts index 1940825..399edb6 100644 --- a/src/api/util/utility/ipAddress.ts +++ b/src/api/util/utility/ipAddress.ts @@ -18,14 +18,14 @@ type Location = { latitude: number; longitude: number }; export function distanceBetweenLocations(loc1: Location, loc2: Location): number { - return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude); + return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude); } //Haversine function function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) { - const p = 0.017453292519943295; // Math.PI / 180 - const c = Math.cos; - const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; + const p = 0.017453292519943295; // Math.PI / 180 + const c = Math.cos; + const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; - return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km + return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km } diff --git a/src/api/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts index beb277b..7a9d4c5 100644 --- a/src/api/util/utility/passwordStrength.ts +++ b/src/api/util/utility/passwordStrength.ts @@ -35,43 +35,43 @@ * Returns: 0 > pw > 1 */ export function checkPassword(password: string): number { - const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; - let strength = 0; + const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; + let strength = 0; - // checks for total password len - if (password.length >= minLength - 1) { - strength += 0.05; - } + // checks for total password len + if (password.length >= minLength - 1) { + strength += 0.05; + } - // checks for amount of Numbers - if (password.match(reNUMBER)?.length ?? 0 >= minNumbers - 1) { - strength += 0.05; - } + // checks for amount of Numbers + if (password.match(reNUMBER)?.length ?? 0 >= minNumbers - 1) { + strength += 0.05; + } - // checks for amount of Uppercase Letters - if (password.match(reUPPERCASELETTER)?.length ?? 0 >= minUpperCase - 1) { - strength += 0.05; - } + // checks for amount of Uppercase Letters + if (password.match(reUPPERCASELETTER)?.length ?? 0 >= minUpperCase - 1) { + strength += 0.05; + } - // checks for amount of symbols - if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) { - strength += 0.05; - } + // checks for amount of symbols + if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) { + strength += 0.05; + } - // checks if password only consists of numbers or only consists of chars - if (password.length == password.match(reNUMBER)?.length || password.length === password.match(reUPPERCASELETTER)?.length) { - strength = 0; - } + // checks if password only consists of numbers or only consists of chars + if (password.length == password.match(reNUMBER)?.length || password.length === password.match(reUPPERCASELETTER)?.length) { + strength = 0; + } - const entropyMap: { [key: string]: number } = {}; - for (let i = 0; i < password.length; i++) { - if (entropyMap[password[i]]) entropyMap[password[i]]++; - else entropyMap[password[i]] = 1; - } + const entropyMap: { [key: string]: number } = {}; + for (let i = 0; i < password.length; i++) { + if (entropyMap[password[i]]) entropyMap[password[i]]++; + else entropyMap[password[i]] = 1; + } - const entropies = Object.values(entropyMap); + const entropies = Object.values(entropyMap); - entropies.map((x) => x / entropyMap.length); - strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length); - return strength; + entropies.map((x) => x / entropyMap.length); + strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length); + return strength; } diff --git a/src/bundle/Server.ts b/src/bundle/Server.ts index 520b760..bcc567d 100644 --- a/src/bundle/Server.ts +++ b/src/bundle/Server.ts @@ -41,41 +41,41 @@ const cdn = new CDNServer({ server, port, production, app }); const gateway = new Gateway.Server({ server, port, production }); const webrtc = new Webrtc.Server({ - server: undefined, - port: wrtcWsPort, - production, + server: undefined, + port: wrtcWsPort, + production, }); process.on("SIGTERM", async () => { - console.log("Shutting down due to SIGTERM"); - await gateway.stop(); - await cdn.stop(); - await api.stop(); - await webrtc.stop(); - server.close(); + console.log("Shutting down due to SIGTERM"); + await gateway.stop(); + await cdn.stop(); + await api.stop(); + await webrtc.stop(); + server.close(); }); async function main() { - await initDatabase(); - await Config.init(); + await initDatabase(); + await Config.init(); - const logRequests = process.env["LOG_REQUESTS"] != undefined; - if (logRequests) { - app.use( - morgan("combined", { - skip: (req, res) => { - let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); - if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; - return skip; - }, - }), - ); - } + const logRequests = process.env["LOG_REQUESTS"] != undefined; + if (logRequests) { + app.use( + morgan("combined", { + skip: (req, res) => { + let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); + if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; + return skip; + }, + }), + ); + } - await new Promise((resolve) => server.listen({ port }, () => resolve(undefined))); - await Promise.all([api.start(), cdn.start(), gateway.start(), webrtc.start()]); + await new Promise((resolve) => server.listen({ port }, () => resolve(undefined))); + await Promise.all([api.start(), cdn.start(), gateway.start(), webrtc.start()]); - console.log(`[Server] ${green(`Listening on port ${bold(port)}`)}`); + console.log(`[Server] ${green(`Listening on port ${bold(port)}`)}`); } main().catch(console.error); diff --git a/src/bundle/start.ts b/src/bundle/start.ts index 37c5b24..af288f8 100644 --- a/src/bundle/start.ts +++ b/src/bundle/start.ts @@ -36,85 +36,85 @@ const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1; function getRevInfoOrFail(): { rev: string | null; lastModified: number } { - const rootDir = path.join(__dirname, "../../"); - // sanity check - if (!fs.existsSync(path.join(rootDir, "package.json"))) { - console.log(red("Error: Cannot find package.json in root directory. Are you running from the correct location?")); - } + const rootDir = path.join(__dirname, "../../"); + // sanity check + if (!fs.existsSync(path.join(rootDir, "package.json"))) { + console.log(red("Error: Cannot find package.json in root directory. Are you running from the correct location?")); + } - // use .rev file if it exists - if (fs.existsSync(path.join(__dirname, "../../.rev"))) { - return JSON.parse(fs.readFileSync(path.join(rootDir, ".rev"), "utf-8")); - } + // use .rev file if it exists + if (fs.existsSync(path.join(__dirname, "../../.rev"))) { + return JSON.parse(fs.readFileSync(path.join(rootDir, ".rev"), "utf-8")); + } - // fall back to invoking git - try { - const rev = execSync(`git -C "${rootDir}" rev-parse HEAD`).toString().trim(); - const lastModified = Number(execSync(`git -C "${rootDir}" log -1 --format=%cd --date=unix`).toString().trim()); - return { - rev, - lastModified, - }; - } catch (e) { - return { rev: null, lastModified: 0 }; - } + // fall back to invoking git + try { + const rev = execSync(`git -C "${rootDir}" rev-parse HEAD`).toString().trim(); + const lastModified = Number(execSync(`git -C "${rootDir}" log -1 --format=%cd --date=unix`).toString().trim()); + return { + rev, + lastModified, + }; + } catch (e) { + return { rev: null, lastModified: 0 }; + } } if (cluster.isPrimary) { - const revInfo = getRevInfoOrFail(); - Logo.printLogo().then(() => { - const unformatted = `spacebar-server | !! Pre-release build !!`; - const formatted = `${blueBright("spacebar-server")} | ${redBright("⚠️ Pre-release build ⚠️")}`; - console.log(bold(centerString(unformatted, 86).replace(unformatted, formatted))); + const revInfo = getRevInfoOrFail(); + Logo.printLogo().then(() => { + const unformatted = `spacebar-server | !! Pre-release build !!`; + const formatted = `${blueBright("spacebar-server")} | ${redBright("⚠️ Pre-release build ⚠️")}`; + console.log(bold(centerString(unformatted, 86).replace(unformatted, formatted))); - const shortRev = revInfo.rev ? revInfo.rev.slice(0, 7) : "unknown"; - const unformattedRevisionHeader = `Commit Hash: ${revInfo.rev !== null ? `${revInfo.rev} (${shortRev})` : "Unknown"}`; - const formattedRevisionHeader = `Commit Hash: ${revInfo.rev !== null ? `${cyan(revInfo.rev)} (${yellow(shortRev)})` : "Unknown"}`; - console.log(bold(centerString(unformattedRevisionHeader, 86).replace(unformattedRevisionHeader, formattedRevisionHeader))); + const shortRev = revInfo.rev ? revInfo.rev.slice(0, 7) : "unknown"; + const unformattedRevisionHeader = `Commit Hash: ${revInfo.rev !== null ? `${revInfo.rev} (${shortRev})` : "Unknown"}`; + const formattedRevisionHeader = `Commit Hash: ${revInfo.rev !== null ? `${cyan(revInfo.rev)} (${yellow(shortRev)})` : "Unknown"}`; + console.log(bold(centerString(unformattedRevisionHeader, 86).replace(unformattedRevisionHeader, formattedRevisionHeader))); - const modifiedTime = new Date(revInfo.lastModified * 1000); - const unformattedLastModified = `Last Updated: ${revInfo.lastModified !== 0 ? `${modifiedTime.toUTCString()}` : "Unknown"}`; - const formattedLastModified = `Last Updated: ${revInfo.lastModified !== 0 ? `${cyan(modifiedTime.toUTCString())}` : "Unknown"}`; - console.log(bold(centerString(unformattedLastModified, 86).replace(unformattedLastModified, formattedLastModified))); + const modifiedTime = new Date(revInfo.lastModified * 1000); + const unformattedLastModified = `Last Updated: ${revInfo.lastModified !== 0 ? `${modifiedTime.toUTCString()}` : "Unknown"}`; + const formattedLastModified = `Last Updated: ${revInfo.lastModified !== 0 ? `${cyan(modifiedTime.toUTCString())}` : "Unknown"}`; + console.log(bold(centerString(unformattedLastModified, 86).replace(unformattedLastModified, formattedLastModified))); - if (revInfo.rev == null) { - console.log(yellow(`Warning: Git is not installed or not in PATH, or the server is not running from a Git repository.`)); - } + if (revInfo.rev == null) { + console.log(yellow(`Warning: Git is not installed or not in PATH, or the server is not running from a Git repository.`)); + } - console.log(`Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).)`); - initStats(); + console.log(`Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).)`); + initStats(); - console.log(`[Process] Starting with ${cores} threads`); + console.log(`[Process] Starting with ${cores} threads`); - if (cores === 1) { - require("./Server"); - } else { - process.env.EVENT_TRANSMISSION = "process"; + if (cores === 1) { + require("./Server"); + } else { + process.env.EVENT_TRANSMISSION = "process"; - // Fork workers. - for (let i = 0; i < cores; i++) { - // Delay each worker start if using sqlite database to prevent locking it - const delay = process.env.DATABASE?.includes("://") ? 0 : i * 1000; - setTimeout(() => { - cluster.fork(); - console.log(`[Process] Worker ${cyan(i)} started.`); - }, delay); - } + // Fork workers. + for (let i = 0; i < cores; i++) { + // Delay each worker start if using sqlite database to prevent locking it + const delay = process.env.DATABASE?.includes("://") ? 0 : i * 1000; + setTimeout(() => { + cluster.fork(); + console.log(`[Process] Worker ${cyan(i)} started.`); + }, delay); + } - cluster.on("message", (sender: Worker, message) => { - for (const id in cluster.workers) { - const worker = cluster.workers[id]; - if (worker === sender || !worker) continue; - worker.send(message); - } - }); + cluster.on("message", (sender: Worker, message) => { + for (const id in cluster.workers) { + const worker = cluster.workers[id]; + if (worker === sender || !worker) continue; + worker.send(message); + } + }); - cluster.on("exit", (worker) => { - console.log(`[Worker] ${red(`PID ${worker.process.pid} died, restarting ...`)}`); - cluster.fork(); - }); - } - }); + cluster.on("exit", (worker) => { + console.log(`[Worker] ${red(`PID ${worker.process.pid} died, restarting ...`)}`); + cluster.fork(); + }); + } + }); } else { - require("./Server"); + require("./Server"); } diff --git a/src/bundle/stats.ts b/src/bundle/stats.ts index dec5e2e..d4ad817 100644 --- a/src/bundle/stats.ts +++ b/src/bundle/stats.ts @@ -21,39 +21,39 @@ import { red } from "picocolors"; export function initStats() { - console.log(`[Path] Running in ${process.cwd()}`); - console.log(`[Path] Running from ${__dirname}`); - try { - console.log(`[CPU] ${os.cpus()[0].model} (x${os.cpus().length})`); - } catch { - console.log("[CPU] Failed to get CPU model!"); - } + console.log(`[Path] Running in ${process.cwd()}`); + console.log(`[Path] Running from ${__dirname}`); + try { + console.log(`[CPU] ${os.cpus()[0].model} (x${os.cpus().length})`); + } catch { + console.log("[CPU] Failed to get CPU model!"); + } - console.log(`[System] ${os.platform()} ${os.release()} ${os.arch()}`); - if (os.platform() == "linux") { - try { - const osReleaseLines = readFileSync("/etc/os-release", "utf8").split("\n"); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - const osRelease: { [key: string]: string } = {}; - for (const line of osReleaseLines) { - if (!line) continue; - const [key, value] = line.match(/(.*?)="?([^"]*)"?/)!.slice(1); - osRelease[key] = value; - } - console.log(`[System]\x1b[${osRelease.ANSI_COLOR}m ${osRelease.NAME ?? "Unknown"} ${osRelease.VERSION ?? "Unknown"} (${osRelease.BUILD_ID ?? "No build ID"})\x1b[0m`); - } catch (e) { - console.log("[System] Unknown Linux distribution (missing /etc/os-release)"); - console.log(e); - } - } - console.log(`[Process] Running with PID: ${process.pid}`); - if (process.getuid && process.getuid() === 0) { - console.warn( - red( - `[Process] Warning Spacebar is running as root, this highly discouraged and might expose your system vulnerable to attackers.` + - `Please run Spacebar as a user without root privileges.`, - ), - ); - } + console.log(`[System] ${os.platform()} ${os.release()} ${os.arch()}`); + if (os.platform() == "linux") { + try { + const osReleaseLines = readFileSync("/etc/os-release", "utf8").split("\n"); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const osRelease: { [key: string]: string } = {}; + for (const line of osReleaseLines) { + if (!line) continue; + const [key, value] = line.match(/(.*?)="?([^"]*)"?/)!.slice(1); + osRelease[key] = value; + } + console.log(`[System]\x1b[${osRelease.ANSI_COLOR}m ${osRelease.NAME ?? "Unknown"} ${osRelease.VERSION ?? "Unknown"} (${osRelease.BUILD_ID ?? "No build ID"})\x1b[0m`); + } catch (e) { + console.log("[System] Unknown Linux distribution (missing /etc/os-release)"); + console.log(e); + } + } + console.log(`[Process] Running with PID: ${process.pid}`); + if (process.getuid && process.getuid() === 0) { + console.warn( + red( + `[Process] Warning Spacebar is running as root, this highly discouraged and might expose your system vulnerable to attackers.` + + `Please run Spacebar as a user without root privileges.`, + ), + ); + } } diff --git a/src/cdn/Server.ts b/src/cdn/Server.ts index d92ec6c..091621f 100644 --- a/src/cdn/Server.ts +++ b/src/cdn/Server.ts @@ -29,105 +29,105 @@ export type CDNServerOptions = ServerOptions; export class CDNServer extends Server { - declare public options: CDNServerOptions; + declare public options: CDNServerOptions; - constructor(options?: Partial) { - super(options); - } + constructor(options?: Partial) { + super(options); + } - async start() { - await initDatabase(); - await Config.init(); - await this.cleanupSignaturesInDb(); + async start() { + await initDatabase(); + await Config.init(); + await this.cleanupSignaturesInDb(); - const logRequests = process.env["LOG_REQUESTS"] != undefined; - if (logRequests) { - this.app.use( - morgan("combined", { - skip: (req, res) => { - let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); - if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; - return skip; - }, - }), - ); - } + const logRequests = process.env["LOG_REQUESTS"] != undefined; + if (logRequests) { + this.app.use( + morgan("combined", { + skip: (req, res) => { + let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); + if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; + return skip; + }, + }), + ); + } - const trustedProxies = Config.get().security.trustedProxies; - if (trustedProxies) this.app.set("trust proxy", trustedProxies); + const trustedProxies = Config.get().security.trustedProxies; + if (trustedProxies) this.app.set("trust proxy", trustedProxies); - this.app.disable("x-powered-by"); + this.app.disable("x-powered-by"); - this.app.use(CORS); - this.app.use(BodyParser({ inflate: true, limit: "10mb" })); + this.app.use(CORS); + this.app.use(BodyParser({ inflate: true, limit: "10mb" })); - await registerRoutes(this, path.join(__dirname, "routes/")); + await registerRoutes(this, path.join(__dirname, "routes/")); - this.app.use("/icons/", avatarsRoute); - console.log("[Server] Route /icons registered"); + this.app.use("/icons/", avatarsRoute); + console.log("[Server] Route /icons registered"); - this.app.use("/role-icons/", iconsRoute); - console.log("[Server] Route /role-icons registered"); + this.app.use("/role-icons/", iconsRoute); + console.log("[Server] Route /role-icons registered"); - this.app.use("/emojis/", avatarsRoute); - console.log("[Server] Route /emojis registered"); + this.app.use("/emojis/", avatarsRoute); + console.log("[Server] Route /emojis registered"); - this.app.use("/stickers/", avatarsRoute); - console.log("[Server] Route /stickers registered"); + this.app.use("/stickers/", avatarsRoute); + console.log("[Server] Route /stickers registered"); - this.app.use("/banners/", avatarsRoute); - console.log("[Server] Route /banners registered"); + this.app.use("/banners/", avatarsRoute); + console.log("[Server] Route /banners registered"); - this.app.use("/splashes/", avatarsRoute); - console.log("[Server] Route /splashes registered"); + this.app.use("/splashes/", avatarsRoute); + console.log("[Server] Route /splashes registered"); - this.app.use("/discovery-splashes/", avatarsRoute); - console.log("[Server] Route /discovery-splashes registered"); + this.app.use("/discovery-splashes/", avatarsRoute); + console.log("[Server] Route /discovery-splashes registered"); - this.app.use("/app-icons/", avatarsRoute); - console.log("[Server] Route /app-icons registered"); + this.app.use("/app-icons/", avatarsRoute); + console.log("[Server] Route /app-icons registered"); - this.app.use("/app-assets/", avatarsRoute); - console.log("[Server] Route /app-assets registered"); + this.app.use("/app-assets/", avatarsRoute); + console.log("[Server] Route /app-assets registered"); - this.app.use("/discover-splashes/", avatarsRoute); - console.log("[Server] Route /discover-splashes registered"); + this.app.use("/discover-splashes/", avatarsRoute); + console.log("[Server] Route /discover-splashes registered"); - this.app.use("/team-icons/", avatarsRoute); - console.log("[Server] Route /team-icons registered"); + this.app.use("/team-icons/", avatarsRoute); + console.log("[Server] Route /team-icons registered"); - this.app.use("/channel-icons/", avatarsRoute); - console.log("[Server] Route /channel-icons registered"); + this.app.use("/channel-icons/", avatarsRoute); + console.log("[Server] Route /channel-icons registered"); - this.app.use("/guilds/:guild_id/users/:user_id/avatars", guildProfilesRoute); - console.log("[Server] Route /guilds/avatars registered"); + this.app.use("/guilds/:guild_id/users/:user_id/avatars", guildProfilesRoute); + console.log("[Server] Route /guilds/avatars registered"); - this.app.use("/guilds/:guild_id/users/:user_id/banners", guildProfilesRoute); - console.log("[Server] Route /guilds/banners registered"); + this.app.use("/guilds/:guild_id/users/:user_id/banners", guildProfilesRoute); + console.log("[Server] Route /guilds/banners registered"); - return super.start(); - } + return super.start(); + } - async stop() { - return super.stop(); - } + async stop() { + return super.stop(); + } - async cleanupSignaturesInDb() { - console.log("[CDN] Cleaning up signatures in database"); - const attachmentsToFix = await Attachment.find({ - where: { url: Like("%?ex=%") }, - }); - if (attachmentsToFix.length === 0) { - console.log("[CDN] No attachments to fix"); - return; - } + async cleanupSignaturesInDb() { + console.log("[CDN] Cleaning up signatures in database"); + const attachmentsToFix = await Attachment.find({ + where: { url: Like("%?ex=%") }, + }); + if (attachmentsToFix.length === 0) { + console.log("[CDN] No attachments to fix"); + return; + } - console.log("[CDN] Found", attachmentsToFix.length, " attachments to fix"); - for (const attachment of attachmentsToFix) { - attachment.url = attachment.url.split("?ex=")[0]; - attachment.proxy_url = attachment.proxy_url?.split("?ex=")[0]; - await attachment.save(); - console.log(`[CDN] Fixed attachment ${attachment.id}`); - } - } + console.log("[CDN] Found", attachmentsToFix.length, " attachments to fix"); + for (const attachment of attachmentsToFix) { + attachment.url = attachment.url.split("?ex=")[0]; + attachment.proxy_url = attachment.proxy_url?.split("?ex=")[0]; + await attachment.save(); + console.log(`[CDN] Fixed attachment ${attachment.id}`); + } + } } diff --git a/src/cdn/routes/attachments.ts b/src/cdn/routes/attachments.ts index 72857e9..db2b950 100644 --- a/src/cdn/routes/attachments.ts +++ b/src/cdn/routes/attachments.ts @@ -30,196 +30,196 @@ const SANITIZED_CONTENT_TYPE = ["text/html", "text/mhtml", "multipart/related", "application/xhtml+xml"]; router.post("/:channel_id", multer.single("file"), async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("file missing"); + if (!req.file) throw new HTTPError("file missing"); - const { buffer, mimetype, size, originalname } = req.file; - const { channel_id } = req.params; - const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); - const id = Snowflake.generate(); - const path = `attachments/${channel_id}/${id}/${filename}`; + const { buffer, mimetype, size, originalname } = req.file; + const { channel_id } = req.params; + const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); + const id = Snowflake.generate(); + const path = `attachments/${channel_id}/${id}/${filename}`; - const endpoint = Config.get()?.cdn.endpointPublic; + const endpoint = Config.get()?.cdn.endpointPublic; - await storage.set(path, buffer); - let width; - let height; - if (mimetype.includes("image")) { - const dimensions = imageSize(buffer); - if (dimensions) { - width = dimensions.width; - height = dimensions.height; - } - } + await storage.set(path, buffer); + let width; + let height; + if (mimetype.includes("image")) { + const dimensions = imageSize(buffer); + if (dimensions) { + width = dimensions.width; + height = dimensions.height; + } + } - const finalUrl = `${endpoint}/${path}`; + const finalUrl = `${endpoint}/${path}`; - const file = { - id, - content_type: mimetype, - filename: filename, - size, - url: finalUrl, - path, - width, - height, - }; + const file = { + id, + content_type: mimetype, + filename: filename, + size, + url: finalUrl, + path, + width, + height, + }; - return res.json(file); + return res.json(file); }); router.get("/:channel_id/:id/:filename", async (req: Request, res: Response) => { - const { channel_id, id, filename } = req.params; - // const { format } = req.query; + const { channel_id, id, filename } = req.params; + // const { format } = req.query; - const path = `attachments/${channel_id}/${id}/${filename}`; + const path = `attachments/${channel_id}/${id}/${filename}`; - const fullUrl = (req.headers["x-forwarded-proto"] ?? req.protocol) + "://" + (req.headers["x-forwarded-host"] ?? req.hostname) + req.originalUrl; + const fullUrl = (req.headers["x-forwarded-proto"] ?? req.protocol) + "://" + (req.headers["x-forwarded-host"] ?? req.hostname) + req.originalUrl; - if ( - Config.get().security.cdnSignUrls && - !hasValidSignature( - new NewUrlUserSignatureData({ - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - UrlSignResult.fromUrl(fullUrl), - ) - ) { - return res.status(404).send("This content is no longer available."); - } + if ( + Config.get().security.cdnSignUrls && + !hasValidSignature( + new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + UrlSignResult.fromUrl(fullUrl), + ) + ) { + return res.status(404).send("This content is no longer available."); + } - const file = await storage.get(path); - if (!file) throw new HTTPError("File not found"); - const type = await fileTypeFromBuffer(file); - let content_type = type?.mime || "application/octet-stream"; + const file = await storage.get(path); + if (!file) throw new HTTPError("File not found"); + const type = await fileTypeFromBuffer(file); + let content_type = type?.mime || "application/octet-stream"; - if (SANITIZED_CONTENT_TYPE.includes(content_type)) { - content_type = "application/octet-stream"; - } + if (SANITIZED_CONTENT_TYPE.includes(content_type)) { + content_type = "application/octet-stream"; + } - res.set("Content-Type", content_type); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", content_type); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); + return res.send(file); }); router.delete("/:channel_id/:id/:filename", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - const { channel_id, id, filename } = req.params; - const path = `attachments/${channel_id}/${id}/${filename}`; + const { channel_id, id, filename } = req.params; + const path = `attachments/${channel_id}/${id}/${filename}`; - await storage.delete(path); + await storage.delete(path); - return res.send({ success: true }); + return res.send({ success: true }); }); // "cloud attachments" router.put("/:channel_id/:batch_id/:attachment_id/:filename", multer.single("file"), async (req: Request, res: Response) => { - const { channel_id, batch_id, attachment_id, filename } = req.params; - const att = await CloudAttachment.findOneOrFail({ - where: { - uploadFilename: `${channel_id}/${batch_id}/${attachment_id}/${filename}`, - channelId: channel_id, - userAttachmentId: attachment_id, - userFilename: filename, - }, - }); + const { channel_id, batch_id, attachment_id, filename } = req.params; + const att = await CloudAttachment.findOneOrFail({ + where: { + uploadFilename: `${channel_id}/${batch_id}/${attachment_id}/${filename}`, + channelId: channel_id, + userAttachmentId: attachment_id, + userFilename: filename, + }, + }); - const maxLength = Config.get().cdn.maxAttachmentSize; + const maxLength = Config.get().cdn.maxAttachmentSize; - console.log("[Cloud Upload] Uploading attachment", att.id, att.userFilename, `Max size: ${maxLength} bytes`); + console.log("[Cloud Upload] Uploading attachment", att.id, att.userFilename, `Max size: ${maxLength} bytes`); - const chunks: Buffer[] = []; - let length = 0; + const chunks: Buffer[] = []; + let length = 0; - req.on("data", (chunk) => { - console.log(`[Cloud Upload] Received chunk of size ${chunk.length} bytes`); - chunks.push(chunk); - length += chunk.length; - if (length > maxLength) { - res.status(413).send("File too large"); - req.destroy(); - } - }); - req.on("end", async () => { - console.log(`[Cloud Upload] Finished receiving file, total size ${length} bytes`); - const buffer = Buffer.concat(chunks); - const path = `attachments/${channel_id}/${batch_id}/${attachment_id}/${filename}`; + req.on("data", (chunk) => { + console.log(`[Cloud Upload] Received chunk of size ${chunk.length} bytes`); + chunks.push(chunk); + length += chunk.length; + if (length > maxLength) { + res.status(413).send("File too large"); + req.destroy(); + } + }); + req.on("end", async () => { + console.log(`[Cloud Upload] Finished receiving file, total size ${length} bytes`); + const buffer = Buffer.concat(chunks); + const path = `attachments/${channel_id}/${batch_id}/${attachment_id}/${filename}`; - await storage.set(path, buffer); + await storage.set(path, buffer); - let mimeType = att.userOriginalContentType; - if (att.userOriginalContentType === null) { - const ft = await fileTypeFromBuffer(buffer); - mimeType = att.contentType = ft?.mime || "application/octet-stream"; - } + let mimeType = att.userOriginalContentType; + if (att.userOriginalContentType === null) { + const ft = await fileTypeFromBuffer(buffer); + mimeType = att.contentType = ft?.mime || "application/octet-stream"; + } - if (mimeType?.includes("image")) { - const dimensions = imageSize(buffer); - if (dimensions) { - att.width = dimensions.width; - att.height = dimensions.height; - } - } + if (mimeType?.includes("image")) { + const dimensions = imageSize(buffer); + if (dimensions) { + att.width = dimensions.width; + att.height = dimensions.height; + } + } - att.size = buffer.length; - await att.save(); + att.size = buffer.length; + await att.save(); - console.log("[Cloud Upload] Saved attachment", att.id, att.userFilename); - res.status(200).end(); - }); + console.log("[Cloud Upload] Saved attachment", att.id, att.userFilename); + res.status(200).end(); + }); }); router.delete("/:channel_id/:batch_id/:attachment_id/:filename", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - console.log("[Cloud Delete] Deleting attachment", req.params); + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + console.log("[Cloud Delete] Deleting attachment", req.params); - const { channel_id, batch_id, attachment_id, filename } = req.params; - const path = `attachments/${channel_id}/${batch_id}/${attachment_id}/${filename}`; + const { channel_id, batch_id, attachment_id, filename } = req.params; + const path = `attachments/${channel_id}/${batch_id}/${attachment_id}/${filename}`; - const att = await CloudAttachment.findOne({ - where: { - uploadFilename: `${channel_id}/${batch_id}/${attachment_id}/${filename}`, - channelId: channel_id, - userAttachmentId: attachment_id, - userFilename: filename, - }, - }); + const att = await CloudAttachment.findOne({ + where: { + uploadFilename: `${channel_id}/${batch_id}/${attachment_id}/${filename}`, + channelId: channel_id, + userAttachmentId: attachment_id, + userFilename: filename, + }, + }); - if (att) { - await att.remove(); - await storage.delete(path); - return res.send({ success: true }); - } - return res.status(404).send("Attachment not found"); + if (att) { + await att.remove(); + await storage.delete(path); + return res.send({ success: true }); + } + return res.status(404).send("Attachment not found"); }); router.post("/:channel_id/:batch_id/:attachment_id/:filename/clone_to_message/:message_id", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - console.log("[Cloud Clone] Cloning attachment to message", req.params); + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + console.log("[Cloud Clone] Cloning attachment to message", req.params); - const { channel_id, batch_id, attachment_id, filename, message_id } = req.params; - const path = `attachments/${channel_id}/${batch_id}/${attachment_id}/${filename}`; - const newPath = `attachments/${channel_id}/${message_id}/${filename}`; + const { channel_id, batch_id, attachment_id, filename, message_id } = req.params; + const path = `attachments/${channel_id}/${batch_id}/${attachment_id}/${filename}`; + const newPath = `attachments/${channel_id}/${message_id}/${filename}`; - const att = await CloudAttachment.findOne({ - where: { - uploadFilename: `${channel_id}/${batch_id}/${attachment_id}/${filename}`, - channelId: channel_id, - userAttachmentId: attachment_id, - userFilename: filename, - }, - }); + const att = await CloudAttachment.findOne({ + where: { + uploadFilename: `${channel_id}/${batch_id}/${attachment_id}/${filename}`, + channelId: channel_id, + userAttachmentId: attachment_id, + userFilename: filename, + }, + }); - if (att) { - await storage.clone(path, newPath); - return res.send({ success: true, new_path: newPath }); - } + if (att) { + await storage.clone(path, newPath); + return res.send({ success: true, new_path: newPath }); + } - return res.status(404).send("Attachment not found"); + return res.status(404).send("Attachment not found"); }); export default router; diff --git a/src/cdn/routes/avatars.ts b/src/cdn/routes/avatars.ts index 8866bef..c770007 100644 --- a/src/cdn/routes/avatars.ts +++ b/src/cdn/routes/avatars.ts @@ -36,71 +36,71 @@ const router = Router({ mergeParams: true }); router.post("/:user_id", multer.single("file"), async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, size } = req.file; - const { user_id } = req.params; + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, size } = req.file; + const { user_id } = req.params; - let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); - const type = await fileTypeFromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); - if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash + const type = await fileTypeFromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); + if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash - const path = `avatars/${user_id}/${hash}`; - const endpoint = Config.get().cdn.endpointPublic; + const path = `avatars/${user_id}/${hash}`; + const endpoint = Config.get().cdn.endpointPublic; - await storage.set(path, buffer); + await storage.set(path, buffer); - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, - }); + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, + }); }); router.get("/:user_id", async (req: Request, res: Response) => { - let { user_id } = req.params; - user_id = user_id.split(".")[0]; // remove .file extension - const path = `avatars/${user_id}`; + let { user_id } = req.params; + user_id = user_id.split(".")[0]; // remove .file extension + const path = `avatars/${user_id}`; - const file = await storage.get(path); - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); + return res.send(file); }); export const getAvatar = async (req: Request, res: Response) => { - const { user_id } = req.params; - let { hash } = req.params; - hash = hash.split(".")[0]; // remove .file extension - const path = `avatars/${user_id}/${hash}`; + const { user_id } = req.params; + let { hash } = req.params; + hash = hash.split(".")[0]; // remove .file extension + const path = `avatars/${user_id}/${hash}`; - const file = await storage.get(path); - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); + return res.send(file); }; router.get("/:user_id/:hash", getAvatar); router.delete("/:user_id/:id", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - const { user_id, id } = req.params; - const path = `avatars/${user_id}/${id}`; + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + const { user_id, id } = req.params; + const path = `avatars/${user_id}/${id}`; - await storage.delete(path); + await storage.delete(path); - return res.send({ success: true }); + return res.send({ success: true }); }); export default router; diff --git a/src/cdn/routes/badge-icons.ts b/src/cdn/routes/badge-icons.ts index 4893c4f..ea7314a 100644 --- a/src/cdn/routes/badge-icons.ts +++ b/src/cdn/routes/badge-icons.ts @@ -24,17 +24,17 @@ const router = Router({ mergeParams: true }); router.get("/:badge_id", async (req: Request, res: Response) => { - const { badge_id } = req.params; - const path = `badge-icons/${badge_id}`; + const { badge_id } = req.params; + const path = `badge-icons/${badge_id}`; - const file = await storage.get(path); - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000, must-revalidate"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000, must-revalidate"); - return res.send(file); + return res.send(file); }); export default router; diff --git a/src/cdn/routes/embed.ts b/src/cdn/routes/embed.ts index 4964efd..55fdc6d 100644 --- a/src/cdn/routes/embed.ts +++ b/src/cdn/routes/embed.ts @@ -23,73 +23,73 @@ import { fileTypeFromBuffer } from "file-type"; const defaultAvatarHashMap = new Map([ - ["0", "4a8562cf00887030c416d3ec2d46385a"], - ["1", "9b0bb198936784c45c72833cc426cc55"], - ["2", "22341bdb500c7b63a93bbce957d1601e"], - ["3", "d9977836b82058bf2f74eebd50edc095"], - ["4", "9d6ddb4e4d899a533a8cc617011351c9"], - ["5", "7213ab6677377974697dfdfbaf5f6a6f"], + ["0", "4a8562cf00887030c416d3ec2d46385a"], + ["1", "9b0bb198936784c45c72833cc426cc55"], + ["2", "22341bdb500c7b63a93bbce957d1601e"], + ["3", "d9977836b82058bf2f74eebd50edc095"], + ["4", "9d6ddb4e4d899a533a8cc617011351c9"], + ["5", "7213ab6677377974697dfdfbaf5f6a6f"], ]); const defaultGroupDMAvatarHashMap = new Map([ - ["0", "3b70bb66089c60f8be5e214bf8574c9d"], - ["1", "9581acd31832465bdeaa5385b0e919a3"], - ["2", "a8a4727cf2dc2939bd3c657fad4463fa"], - ["3", "2e46fe14586f8e95471c0917f56726b5"], - ["4", "fac7e78de9753d4a37083bba74c1d9ef"], - ["5", "4ab900144b0865430dc9be825c838faa"], - ["6", "1276374a404452756f3c9cc2601508a5"], - ["7", "904bf9f1b61f53ef4a3b7a893afeabe3"], + ["0", "3b70bb66089c60f8be5e214bf8574c9d"], + ["1", "9581acd31832465bdeaa5385b0e919a3"], + ["2", "a8a4727cf2dc2939bd3c657fad4463fa"], + ["3", "2e46fe14586f8e95471c0917f56726b5"], + ["4", "fac7e78de9753d4a37083bba74c1d9ef"], + ["5", "4ab900144b0865430dc9be825c838faa"], + ["6", "1276374a404452756f3c9cc2601508a5"], + ["7", "904bf9f1b61f53ef4a3b7a893afeabe3"], ]); const router = Router({ mergeParams: true }); async function getFile(path: string) { - try { - return fs.readFile(path); - } catch (error) { - try { - const files = await fs.readdir(path); - if (!files.length) return null; - return fs.readFile(join(path, files[0])); - } catch (error) { - return null; - } - } + try { + return fs.readFile(path); + } catch (error) { + try { + const files = await fs.readdir(path); + if (!files.length) return null; + return fs.readFile(join(path, files[0])); + } catch (error) { + return null; + } + } } router.get("/avatars/:id", async (req: Request, res: Response) => { - let { id } = req.params; - id = id.split(".")[0]; // remove .file extension - const hash = defaultAvatarHashMap.get(id); - if (!hash) throw new HTTPError("not found", 404); - const path = join(__dirname, "..", "..", "..", "assets", "public", `${hash}.png`); + let { id } = req.params; + id = id.split(".")[0]; // remove .file extension + const hash = defaultAvatarHashMap.get(id); + if (!hash) throw new HTTPError("not found", 404); + const path = join(__dirname, "..", "..", "..", "assets", "public", `${hash}.png`); - const file = await getFile(path); - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + const file = await getFile(path); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); + return res.send(file); }); router.get("/group-avatars/:id", async (req: Request, res: Response) => { - let { id } = req.params; - id = id.split(".")[0]; // remove .file extension - const hash = defaultGroupDMAvatarHashMap.get(id); - if (!hash) throw new HTTPError("not found", 404); - const path = join(__dirname, "..", "..", "..", "assets", "public", `${hash}.png`); + let { id } = req.params; + id = id.split(".")[0]; // remove .file extension + const hash = defaultGroupDMAvatarHashMap.get(id); + if (!hash) throw new HTTPError("not found", 404); + const path = join(__dirname, "..", "..", "..", "assets", "public", `${hash}.png`); - const file = await getFile(path); - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + const file = await getFile(path); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); + return res.send(file); }); export default router; diff --git a/src/cdn/routes/guild-profiles.ts b/src/cdn/routes/guild-profiles.ts index 9f2173d..6660b72 100644 --- a/src/cdn/routes/guild-profiles.ts +++ b/src/cdn/routes/guild-profiles.ts @@ -36,70 +36,70 @@ const router = Router({ mergeParams: true }); router.post("/", multer.single("file"), async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, size } = req.file; - const { guild_id, user_id } = req.params; + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, size } = req.file; + const { guild_id, user_id } = req.params; - let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); - const type = await fileTypeFromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); - if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash + const type = await fileTypeFromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); + if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash - const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`; - const endpoint = Config.get().cdn.endpointPublic; + const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`; + const endpoint = Config.get().cdn.endpointPublic; - await storage.set(path, buffer); + await storage.set(path, buffer); - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, - }); + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, + }); }); router.get("/", async (req: Request, res: Response) => { - const { guild_id } = req.params; - let { user_id } = req.params; - user_id = user_id.split(".")[0]; // remove .file extension - const path = `guilds/${guild_id}/users/${user_id}/avatars`; + const { guild_id } = req.params; + let { user_id } = req.params; + user_id = user_id.split(".")[0]; // remove .file extension + const path = `guilds/${guild_id}/users/${user_id}/avatars`; - const file = await storage.get(path); - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); + return res.send(file); }); router.get("/:hash", async (req: Request, res: Response) => { - const { guild_id, user_id } = req.params; - let { hash } = req.params; - hash = hash.split(".")[0]; // remove .file extension - const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`; + const { guild_id, user_id } = req.params; + let { hash } = req.params; + hash = hash.split(".")[0]; // remove .file extension + const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`; - const file = await storage.get(path); - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); + return res.send(file); }); router.delete("/:id", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - const { guild_id, user_id, id } = req.params; - const path = `guilds/${guild_id}/users/${user_id}/avatars/${id}`; + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + const { guild_id, user_id, id } = req.params; + const path = `guilds/${guild_id}/users/${user_id}/avatars/${id}`; - await storage.delete(path); + await storage.delete(path); - return res.send({ success: true }); + return res.send({ success: true }); }); export default router; diff --git a/src/cdn/routes/ping.ts b/src/cdn/routes/ping.ts index 44d74d4..a3b1007 100644 --- a/src/cdn/routes/ping.ts +++ b/src/cdn/routes/ping.ts @@ -21,7 +21,7 @@ const router = Router({ mergeParams: true }); router.get("/", (req: Request, res: Response) => { - res.send("pong"); + res.send("pong"); }); export default router; diff --git a/src/cdn/routes/role-icons.ts b/src/cdn/routes/role-icons.ts index b88f80a..1e00f98 100644 --- a/src/cdn/routes/role-icons.ts +++ b/src/cdn/routes/role-icons.ts @@ -36,75 +36,75 @@ const router = Router({ mergeParams: true }); router.post("/:role_id", multer.single("file"), async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, size } = req.file; - const { role_id } = req.params; + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, size } = req.file; + const { role_id } = req.params; - const hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + const hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); - const type = await fileTypeFromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); + const type = await fileTypeFromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); - const path = `role-icons/${role_id}/${hash}.png`; - const endpoint = Config.get().cdn.endpointPublic; + const path = `role-icons/${role_id}/${hash}.png`; + const endpoint = Config.get().cdn.endpointPublic; - await storage.set(path, buffer); + await storage.set(path, buffer); - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${role_id}/${hash}`, - }); + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${role_id}/${hash}`, + }); }); router.get("/:role_id", async (req: Request, res: Response) => { - const { role_id } = req.params; - //role_id = role_id.split(".")[0]; // remove .file extension - const path = `role-icons/${role_id}`; + const { role_id } = req.params; + //role_id = role_id.split(".")[0]; // remove .file extension + const path = `role-icons/${role_id}`; - const file = await storage.get(path); - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + const file = await storage.get(path); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000, must-revalidate"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000, must-revalidate"); - return res.send(file); + return res.send(file); }); router.get("/:role_id/:hash", async (req: Request, res: Response) => { - const { role_id, hash } = req.params; - //hash = hash.split(".")[0]; // remove .file extension - const requested_extension = hash.split(".")[1]; - const role_icon_hash = hash.split(".")[0]; - let file: Buffer | null = null; + const { role_id, hash } = req.params; + //hash = hash.split(".")[0]; // remove .file extension + const requested_extension = hash.split(".")[1]; + const role_icon_hash = hash.split(".")[0]; + let file: Buffer | null = null; - const extensions_to_try = [requested_extension, "png", "jpg", "jpeg", "webp", "svg"]; + const extensions_to_try = [requested_extension, "png", "jpg", "jpeg", "webp", "svg"]; - for (let i = 0; i < extensions_to_try.length; i++) { - file = await storage.get(`role-icons/${role_id}/${role_icon_hash}.${extensions_to_try[i]}`); - if (file) break; - } + for (let i = 0; i < extensions_to_try.length; i++) { + file = await storage.get(`role-icons/${role_id}/${role_icon_hash}.${extensions_to_try[i]}`); + if (file) break; + } - if (!file) throw new HTTPError("not found", 404); - const type = await fileTypeFromBuffer(file); + if (!file) throw new HTTPError("not found", 404); + const type = await fileTypeFromBuffer(file); - res.set("Content-Type", type?.mime); - res.set("Cache-Control", "public, max-age=31536000, must-revalidate"); + res.set("Content-Type", type?.mime); + res.set("Cache-Control", "public, max-age=31536000, must-revalidate"); - return res.send(file); + return res.send(file); }); router.delete("/:role_id/:id", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); - const { role_id, id } = req.params; - const path = `role-icons/${role_id}/${id}`; + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + const { role_id, id } = req.params; + const path = `role-icons/${role_id}/${id}`; - await storage.delete(path); + await storage.delete(path); - return res.send({ success: true }); + return res.send({ success: true }); }); export default router; diff --git a/src/cdn/start.ts b/src/cdn/start.ts index 96e1fff..4455806 100644 --- a/src/cdn/start.ts +++ b/src/cdn/start.ts @@ -24,10 +24,10 @@ import { CDNServer } from "./Server"; const server = new CDNServer({ port: Number(process.env.PORT) || 3003 }); server - .start() - .then(() => { - console.log("[Server] started on :" + server.options.port); - }) - .catch((e) => console.error("[Server] Error starting: ", e)); + .start() + .then(() => { + console.log("[Server] started on :" + server.options.port); + }) + .catch((e) => console.error("[Server] Error starting: ", e)); module.exports = server; diff --git a/src/cdn/util/FileStorage.ts b/src/cdn/util/FileStorage.ts index 94f7ab1..9ebe9f7 100644 --- a/src/cdn/util/FileStorage.ts +++ b/src/cdn/util/FileStorage.ts @@ -25,52 +25,52 @@ // TODO: split stored files into separate folders named after cloned route function getPath(path: string) { - // STORAGE_LOCATION has a default value in start.ts - const root = process.env.STORAGE_LOCATION || "../"; - const filename = join(root, path); + // STORAGE_LOCATION has a default value in start.ts + const root = process.env.STORAGE_LOCATION || "../"; + const filename = join(root, path); - if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) throw new Error("invalid path"); - return filename; + if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) throw new Error("invalid path"); + return filename; } export class FileStorage implements Storage { - async get(path: string): Promise { - path = getPath(path); - try { - return fs.readFileSync(path); - } catch (error) { - try { - const files = fs.readdirSync(path); - if (!files.length) return null; - return fs.readFileSync(join(path, files[0])); - } catch (error) { - return null; - } - } - } + async get(path: string): Promise { + path = getPath(path); + try { + return fs.readFileSync(path); + } catch (error) { + try { + const files = fs.readdirSync(path); + if (!files.length) return null; + return fs.readFileSync(join(path, files[0])); + } catch (error) { + return null; + } + } + } - async clone(path: string, newPath: string) { - path = getPath(path); - newPath = getPath(newPath); + async clone(path: string, newPath: string) { + path = getPath(path); + newPath = getPath(newPath); - if (!fs.existsSync(dirname(newPath))) fs.mkdirSync(dirname(newPath), { recursive: true }); + if (!fs.existsSync(dirname(newPath))) fs.mkdirSync(dirname(newPath), { recursive: true }); - // use reflink if possible, in order to not duplicate files at the block layer... - fs.copyFileSync(path, newPath, fs.constants.COPYFILE_FICLONE); - } + // use reflink if possible, in order to not duplicate files at the block layer... + fs.copyFileSync(path, newPath, fs.constants.COPYFILE_FICLONE); + } - async set(path: string, value: Buffer) { - path = getPath(path); - if (!fs.existsSync(dirname(path))) fs.mkdirSync(dirname(path), { recursive: true }); + async set(path: string, value: Buffer) { + path = getPath(path); + if (!fs.existsSync(dirname(path))) fs.mkdirSync(dirname(path), { recursive: true }); - const ret = Readable.from(value); - const cleaned_file = fs.createWriteStream(path); + const ret = Readable.from(value); + const cleaned_file = fs.createWriteStream(path); - ret.pipe(new ExifTransformer()).pipe(cleaned_file); - } + ret.pipe(new ExifTransformer()).pipe(cleaned_file); + } - async delete(path: string) { - //TODO we should delete the parent directory if empty - fs.unlinkSync(getPath(path)); - } + async delete(path: string) { + //TODO we should delete the parent directory if empty + fs.unlinkSync(getPath(path)); + } } diff --git a/src/cdn/util/S3Storage.ts b/src/cdn/util/S3Storage.ts index c3a8d5c..f3ac0b4 100644 --- a/src/cdn/util/S3Storage.ts +++ b/src/cdn/util/S3Storage.ts @@ -21,67 +21,67 @@ import { Storage } from "./Storage"; const readableToBuffer = (readable: Readable): Promise => - new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - readable.on("data", (chunk) => chunks.push(chunk)); - readable.on("error", reject); - readable.on("end", () => resolve(Buffer.concat(chunks))); - }); + new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + readable.on("data", (chunk) => chunks.push(chunk)); + readable.on("error", reject); + readable.on("end", () => resolve(Buffer.concat(chunks))); + }); export class S3Storage implements Storage { - public constructor( - private client: S3, - private bucket: string, - private basePath?: string, - ) {} + public constructor( + private client: S3, + private bucket: string, + private basePath?: string, + ) {} - /** - * Always return a string, to ensure consistency. - */ - get bucketBasePath() { - return this.basePath ?? ""; - } + /** + * Always return a string, to ensure consistency. + */ + get bucketBasePath() { + return this.basePath ?? ""; + } - async set(path: string, data: Buffer): Promise { - await this.client.putObject({ - Bucket: this.bucket, - Key: `${this.bucketBasePath}${path}`, - Body: data, - }); - } + async set(path: string, data: Buffer): Promise { + await this.client.putObject({ + Bucket: this.bucket, + Key: `${this.bucketBasePath}${path}`, + Body: data, + }); + } - async clone(path: string, newPath: string): Promise { - // TODO: does this even work? - await this.client.copyObject({ - Bucket: this.bucket, - CopySource: `/${this.bucket}/${this.bucketBasePath}${path}`, - Key: `${this.bucketBasePath}${newPath}`, - }); - } + async clone(path: string, newPath: string): Promise { + // TODO: does this even work? + await this.client.copyObject({ + Bucket: this.bucket, + CopySource: `/${this.bucket}/${this.bucketBasePath}${path}`, + Key: `${this.bucketBasePath}${newPath}`, + }); + } - async get(path: string): Promise { - try { - const s3Object = await this.client.getObject({ - Bucket: this.bucket, - Key: `${this.bucketBasePath ?? ""}${path}`, - }); + async get(path: string): Promise { + try { + const s3Object = await this.client.getObject({ + Bucket: this.bucket, + Key: `${this.bucketBasePath ?? ""}${path}`, + }); - if (!s3Object.Body) return null; + if (!s3Object.Body) return null; - const body = s3Object.Body; + const body = s3Object.Body; - return await readableToBuffer(body); - } catch (err) { - console.error(`[CDN] Unable to get S3 object at path ${path}.`); - console.error(err); - return null; - } - } + return await readableToBuffer(body); + } catch (err) { + console.error(`[CDN] Unable to get S3 object at path ${path}.`); + console.error(err); + return null; + } + } - async delete(path: string): Promise { - await this.client.deleteObject({ - Bucket: this.bucket, - Key: `${this.bucketBasePath}${path}`, - }); - } + async delete(path: string): Promise { + await this.client.deleteObject({ + Bucket: this.bucket, + Key: `${this.bucketBasePath}${path}`, + }); + } } diff --git a/src/cdn/util/Storage.ts b/src/cdn/util/Storage.ts index 1e04989..6ec1b79 100644 --- a/src/cdn/util/Storage.ts +++ b/src/cdn/util/Storage.ts @@ -24,52 +24,52 @@ process.cwd(); export interface Storage { - set(path: string, data: Buffer): Promise; - clone(path: string, newPath: string): Promise; - get(path: string): Promise; - delete(path: string): Promise; + set(path: string, data: Buffer): Promise; + clone(path: string, newPath: string): Promise; + get(path: string): Promise; + delete(path: string): Promise; } let storage: Storage; if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) { - let location = process.env.STORAGE_LOCATION; - if (location) { - location = path.resolve(location); - } else { - location = path.join(process.cwd(), "files"); - } - // TODO: move this to some start func, so it doesn't run when server is imported - //console.log(`[CDN] storage location: ${bgCyan(`${black(location)}`)}`); - if (!fs.existsSync(location)) fs.mkdirSync(location); - process.env.STORAGE_LOCATION = location; + let location = process.env.STORAGE_LOCATION; + if (location) { + location = path.resolve(location); + } else { + location = path.join(process.cwd(), "files"); + } + // TODO: move this to some start func, so it doesn't run when server is imported + //console.log(`[CDN] storage location: ${bgCyan(`${black(location)}`)}`); + if (!fs.existsSync(location)) fs.mkdirSync(location); + process.env.STORAGE_LOCATION = location; - storage = new FileStorage(); + storage = new FileStorage(); } else if (process.env.STORAGE_PROVIDER === "s3") { - const region = process.env.STORAGE_REGION, - bucket = process.env.STORAGE_BUCKET; + const region = process.env.STORAGE_REGION, + bucket = process.env.STORAGE_BUCKET; - if (!region) { - console.error(`[CDN] You must provide a region when using the S3 storage provider.`); - process.exit(1); - } + if (!region) { + console.error(`[CDN] You must provide a region when using the S3 storage provider.`); + process.exit(1); + } - if (!bucket) { - console.error(`[CDN] You must provide a bucket when using the S3 storage provider.`); - process.exit(1); - } + if (!bucket) { + console.error(`[CDN] You must provide a bucket when using the S3 storage provider.`); + process.exit(1); + } - // in the S3 provider, this should be the root path in the bucket - let location = process.env.STORAGE_LOCATION; + // in the S3 provider, this should be the root path in the bucket + let location = process.env.STORAGE_LOCATION; - if (!location) { - console.warn(`[CDN] STORAGE_LOCATION unconfigured for S3 provider, defaulting to the bucket root...`); - location = undefined; - } + if (!location) { + console.warn(`[CDN] STORAGE_LOCATION unconfigured for S3 provider, defaulting to the bucket root...`); + location = undefined; + } - const client = new S3({ region }); + const client = new S3({ region }); - storage = new S3Storage(client, bucket, location); + storage = new S3Storage(client, bucket, location); } export { storage }; diff --git a/src/cdn/util/multer.ts b/src/cdn/util/multer.ts index 337494e..49cb387 100644 --- a/src/cdn/util/multer.ts +++ b/src/cdn/util/multer.ts @@ -19,10 +19,10 @@ import multerConfig from "multer"; export const multer = multerConfig({ - storage: multerConfig.memoryStorage(), - limits: { - fields: 10, - files: 10, - fileSize: 1024 * 1024 * 100, // 100 mb - }, + storage: multerConfig.memoryStorage(), + limits: { + fields: 10, + files: 10, + fileSize: 1024 * 1024 * 100, // 100 mb + }, }); diff --git a/src/connections/BattleNet/BattleNetSettings.ts b/src/connections/BattleNet/BattleNetSettings.ts index 8fa0748..661fdeb 100644 --- a/src/connections/BattleNet/BattleNetSettings.ts +++ b/src/connections/BattleNet/BattleNetSettings.ts @@ -17,7 +17,7 @@ */ export class BattleNetSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/BattleNet/index.ts b/src/connections/BattleNet/index.ts index 7c5435e..843ab79 100644 --- a/src/connections/BattleNet/index.ts +++ b/src/connections/BattleNet/index.ts @@ -22,9 +22,9 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; interface BattleNetConnectionUser { - sub: string; - id: number; - battletag: string; + sub: string; + id: number; + battletag: string; } // interface BattleNetErrorResponse { @@ -33,93 +33,93 @@ // } export default class BattleNetConnection extends Connection { - public readonly id = "battlenet"; - public readonly authorizeUrl = "https://oauth.battle.net/authorize"; - public readonly tokenUrl = "https://oauth.battle.net/token"; - public readonly userInfoUrl = "https://us.battle.net/oauth/userinfo"; - public readonly scopes = []; - settings: BattleNetSettings = new BattleNetSettings(); + public readonly id = "battlenet"; + public readonly authorizeUrl = "https://oauth.battle.net/authorize"; + public readonly tokenUrl = "https://oauth.battle.net/token"; + public readonly userInfoUrl = "https://us.battle.net/oauth/userinfo"; + public readonly scopes = []; + settings: BattleNetSettings = new BattleNetSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - url.searchParams.append("response_type", "code"); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + url.searchParams.append("response_type", "code"); + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - }) - .body( - new URLSearchParams({ - grant_type: "authorization_code", - code: code, - client_id: this.settings.clientId as string, - client_secret: this.settings.clientSecret as string, - redirect_uri: this.getRedirectUri(), - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + }) + .body( + new URLSearchParams({ + grant_type: "authorization_code", + code: code, + client_id: this.settings.clientId as string, + client_secret: this.settings.clientSecret as string, + redirect_uri: this.getRedirectUri(), + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.id.toString()); + const exists = await this.hasConnection(userId, userInfo.id.toString()); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - user_id: userId, - external_id: userInfo.id.toString(), - friend_sync: params.friend_sync, - name: userInfo.battletag, - type: this.id, - }); - } + return await this.createConnection({ + user_id: userId, + external_id: userInfo.id.toString(), + friend_sync: params.friend_sync, + name: userInfo.battletag, + type: this.id, + }); + } } diff --git a/src/connections/Discord/DiscordSettings.ts b/src/connections/Discord/DiscordSettings.ts index 1263307..af0b8d6 100644 --- a/src/connections/Discord/DiscordSettings.ts +++ b/src/connections/Discord/DiscordSettings.ts @@ -17,7 +17,7 @@ */ export class DiscordSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/Discord/index.ts b/src/connections/Discord/index.ts index 6f87cd2..ab44f4b 100644 --- a/src/connections/Discord/index.ts +++ b/src/connections/Discord/index.ts @@ -22,103 +22,103 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; interface UserResponse { - id: string; - username: string; - discriminator: string; - avatar_url: string | null; + id: string; + username: string; + discriminator: string; + avatar_url: string | null; } export default class DiscordConnection extends Connection { - public readonly id = "discord"; - public readonly authorizeUrl = "https://discord.com/api/oauth2/authorize"; - public readonly tokenUrl = "https://discord.com/api/oauth2/token"; - public readonly userInfoUrl = "https://discord.com/api/users/@me"; - public readonly scopes = ["identify"]; - settings: DiscordSettings = new DiscordSettings(); + public readonly id = "discord"; + public readonly authorizeUrl = "https://discord.com/api/oauth2/authorize"; + public readonly tokenUrl = "https://discord.com/api/oauth2/token"; + public readonly userInfoUrl = "https://discord.com/api/users/@me"; + public readonly scopes = ["identify"]; + settings: DiscordSettings = new DiscordSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("state", state); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("response_type", "code"); - // controls whether, on repeated authorizations, the consent screen is shown - url.searchParams.append("consent", "none"); - url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("state", state); + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("response_type", "code"); + // controls whether, on repeated authorizations, the consent screen is shown + url.searchParams.append("consent", "none"); + url.searchParams.append("redirect_uri", this.getRedirectUri()); - return url.toString(); - } + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); - const url = this.getTokenUrl(); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - }) - .body( - new URLSearchParams({ - client_id: this.settings.clientId as string, - client_secret: this.settings.clientSecret as string, - grant_type: "authorization_code", - code: code, - redirect_uri: this.getRedirectUri(), - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + }) + .body( + new URLSearchParams({ + client_id: this.settings.clientId as string, + client_secret: this.settings.clientSecret as string, + grant_type: "authorization_code", + code: code, + redirect_uri: this.getRedirectUri(), + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.id); + const exists = await this.hasConnection(userId, userInfo.id); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - user_id: userId, - external_id: userInfo.id, - friend_sync: params.friend_sync, - name: `${userInfo.username}#${userInfo.discriminator}`, - type: this.id, - }); - } + return await this.createConnection({ + user_id: userId, + external_id: userInfo.id, + friend_sync: params.friend_sync, + name: `${userInfo.username}#${userInfo.discriminator}`, + type: this.id, + }); + } } diff --git a/src/connections/EpicGames/EpicGamesSettings.ts b/src/connections/EpicGames/EpicGamesSettings.ts index a66b6f4..fbe5c7a 100644 --- a/src/connections/EpicGames/EpicGamesSettings.ts +++ b/src/connections/EpicGames/EpicGamesSettings.ts @@ -17,7 +17,7 @@ */ export class EpicGamesSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/EpicGames/index.ts b/src/connections/EpicGames/index.ts index fdc2aa7..69c44cb 100644 --- a/src/connections/EpicGames/index.ts +++ b/src/connections/EpicGames/index.ts @@ -22,110 +22,110 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; export interface UserResponse { - accountId: string; - displayName: string; - preferredLanguage: string; + accountId: string; + displayName: string; + preferredLanguage: string; } export interface EpicTokenResponse extends ConnectedAccountCommonOAuthTokenResponse { - expires_at: string; - refresh_expires_in: number; - refresh_expires_at: string; - account_id: string; - client_id: string; - application_id: string; + expires_at: string; + refresh_expires_in: number; + refresh_expires_at: string; + account_id: string; + client_id: string; + application_id: string; } export default class EpicGamesConnection extends Connection { - public readonly id = "epicgames"; - public readonly authorizeUrl = "https://www.epicgames.com/id/authorize"; - public readonly tokenUrl = "https://api.epicgames.dev/epic/oauth/v1/token"; - public readonly userInfoUrl = "https://api.epicgames.dev/epic/id/v1/accounts"; - public readonly scopes = ["basic profile"]; - settings: EpicGamesSettings = new EpicGamesSettings(); + public readonly id = "epicgames"; + public readonly authorizeUrl = "https://www.epicgames.com/id/authorize"; + public readonly tokenUrl = "https://api.epicgames.dev/epic/oauth/v1/token"; + public readonly userInfoUrl = "https://api.epicgames.dev/epic/id/v1/accounts"; + public readonly scopes = ["basic profile"]; + settings: EpicGamesSettings = new EpicGamesSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("response_type", "code"); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - Authorization: `Basic ${Buffer.from(`${this.settings.clientId}:${this.settings.clientSecret}`).toString("base64")}`, - "Content-Type": "application/x-www-form-urlencoded", - }) - .body( - new URLSearchParams({ - grant_type: "authorization_code", - code, - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + Authorization: `Basic ${Buffer.from(`${this.settings.clientId}:${this.settings.clientSecret}`).toString("base64")}`, + "Content-Type": "application/x-www-form-urlencoded", + }) + .body( + new URLSearchParams({ + grant_type: "authorization_code", + code, + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const { sub } = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString("utf8")); - const url = new URL(this.userInfoUrl); - url.searchParams.append("accountId", sub); + async getUser(token: string): Promise { + const { sub } = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString("utf8")); + const url = new URL(this.userInfoUrl); + url.searchParams.append("accountId", sub); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo[0].accountId); + const exists = await this.hasConnection(userId, userInfo[0].accountId); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - user_id: userId, - external_id: userInfo[0].accountId, - friend_sync: params.friend_sync, - name: userInfo[0].displayName, - type: this.id, - }); - } + return await this.createConnection({ + user_id: userId, + external_id: userInfo[0].accountId, + friend_sync: params.friend_sync, + name: userInfo[0].displayName, + type: this.id, + }); + } } diff --git a/src/connections/Facebook/FacebookSettings.ts b/src/connections/Facebook/FacebookSettings.ts index 17811a1..f130358 100644 --- a/src/connections/Facebook/FacebookSettings.ts +++ b/src/connections/Facebook/FacebookSettings.ts @@ -17,7 +17,7 @@ */ export class FacebookSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/Facebook/index.ts b/src/connections/Facebook/index.ts index 50552d8..b661cbe 100644 --- a/src/connections/Facebook/index.ts +++ b/src/connections/Facebook/index.ts @@ -22,105 +22,105 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; export interface FacebookErrorResponse { - error: { - message: string; - type: string; - code: number; - fbtrace_id: string; - }; + error: { + message: string; + type: string; + code: number; + fbtrace_id: string; + }; } interface UserResponse { - name: string; - id: string; + name: string; + id: string; } export default class FacebookConnection extends Connection { - public readonly id = "facebook"; - public readonly authorizeUrl = "https://www.facebook.com/v14.0/dialog/oauth"; - public readonly tokenUrl = "https://graph.facebook.com/v14.0/oauth/access_token"; - public readonly userInfoUrl = "https://graph.facebook.com/v14.0/me"; - public readonly scopes = ["public_profile"]; - settings: FacebookSettings = new FacebookSettings(); + public readonly id = "facebook"; + public readonly authorizeUrl = "https://www.facebook.com/v14.0/dialog/oauth"; + public readonly tokenUrl = "https://graph.facebook.com/v14.0/oauth/access_token"; + public readonly userInfoUrl = "https://graph.facebook.com/v14.0/me"; + public readonly scopes = ["public_profile"]; + settings: FacebookSettings = new FacebookSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("state", state); - url.searchParams.append("response_type", "code"); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("display", "popup"); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("state", state); + url.searchParams.append("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("display", "popup"); + return url.toString(); + } - getTokenUrl(code: string): string { - const url = new URL(this.tokenUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("client_secret", this.settings.clientSecret as string); - url.searchParams.append("code", code); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - return url.toString(); - } + getTokenUrl(code: string): string { + const url = new URL(this.tokenUrl); + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("client_secret", this.settings.clientSecret as string); + url.searchParams.append("code", code); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + return url.toString(); + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(code); + const url = this.getTokenUrl(code); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.id); + const exists = await this.hasConnection(userId, userInfo.id); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - user_id: userId, - external_id: userInfo.id, - friend_sync: params.friend_sync, - name: userInfo.name, - type: this.id, - }); - } + return await this.createConnection({ + user_id: userId, + external_id: userInfo.id, + friend_sync: params.friend_sync, + name: userInfo.name, + type: this.id, + }); + } } diff --git a/src/connections/GitHub/GitHubSettings.ts b/src/connections/GitHub/GitHubSettings.ts index ef5fe40..f744a77 100644 --- a/src/connections/GitHub/GitHubSettings.ts +++ b/src/connections/GitHub/GitHubSettings.ts @@ -17,7 +17,7 @@ */ export class GitHubSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/GitHub/index.ts b/src/connections/GitHub/index.ts index d29bdc3..42dc072 100644 --- a/src/connections/GitHub/index.ts +++ b/src/connections/GitHub/index.ts @@ -22,94 +22,94 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; interface UserResponse { - login: string; - id: number; - name: string; + login: string; + id: number; + name: string; } export default class GitHubConnection extends Connection { - public readonly id = "github"; - public readonly authorizeUrl = "https://github.com/login/oauth/authorize"; - public readonly tokenUrl = "https://github.com/login/oauth/access_token"; - public readonly userInfoUrl = "https://api.github.com/user"; - public readonly scopes = ["read:user"]; - settings: GitHubSettings = new GitHubSettings(); + public readonly id = "github"; + public readonly authorizeUrl = "https://github.com/login/oauth/authorize"; + public readonly tokenUrl = "https://github.com/login/oauth/access_token"; + public readonly userInfoUrl = "https://api.github.com/user"; + public readonly scopes = ["read:user"]; + settings: GitHubSettings = new GitHubSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + return url.toString(); + } - getTokenUrl(code: string): string { - const url = new URL(this.tokenUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("client_secret", this.settings.clientSecret as string); - url.searchParams.append("code", code); - return url.toString(); - } + getTokenUrl(code: string): string { + const url = new URL(this.tokenUrl); + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("client_secret", this.settings.clientSecret as string); + url.searchParams.append("code", code); + return url.toString(); + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(code); + const url = this.getTokenUrl(code); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - }) + return wretch(url.toString()) + .headers({ + Accept: "application/json", + }) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.id.toString()); + const exists = await this.hasConnection(userId, userInfo.id.toString()); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - user_id: userId, - external_id: userInfo.id.toString(), - friend_sync: params.friend_sync, - name: userInfo.login, - type: this.id, - }); - } + return await this.createConnection({ + user_id: userId, + external_id: userInfo.id.toString(), + friend_sync: params.friend_sync, + name: userInfo.login, + type: this.id, + }); + } } diff --git a/src/connections/Reddit/RedditSettings.ts b/src/connections/Reddit/RedditSettings.ts index 3d1bf8b..083af8e 100644 --- a/src/connections/Reddit/RedditSettings.ts +++ b/src/connections/Reddit/RedditSettings.ts @@ -17,7 +17,7 @@ */ export class RedditSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/Reddit/index.ts b/src/connections/Reddit/index.ts index 5f8dfbb..14faf4a 100644 --- a/src/connections/Reddit/index.ts +++ b/src/connections/Reddit/index.ts @@ -22,114 +22,114 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; export interface UserResponse { - verified: boolean; - coins: number; - id: string; - is_mod: boolean; - has_verified_email: boolean; - total_karma: number; - name: string; - created: number; - gold_creddits: number; - created_utc: number; + verified: boolean; + coins: number; + id: string; + is_mod: boolean; + has_verified_email: boolean; + total_karma: number; + name: string; + created: number; + gold_creddits: number; + created_utc: number; } export interface ErrorResponse { - message: string; - error: number; + message: string; + error: number; } export default class RedditConnection extends Connection { - public readonly id = "reddit"; - public readonly authorizeUrl = "https://www.reddit.com/api/v1/authorize"; - public readonly tokenUrl = "https://www.reddit.com/api/v1/access_token"; - public readonly userInfoUrl = "https://oauth.reddit.com/api/v1/me"; - public readonly scopes = ["identity"]; - settings: RedditSettings = new RedditSettings(); + public readonly id = "reddit"; + public readonly authorizeUrl = "https://www.reddit.com/api/v1/authorize"; + public readonly tokenUrl = "https://www.reddit.com/api/v1/access_token"; + public readonly userInfoUrl = "https://oauth.reddit.com/api/v1/me"; + public readonly scopes = ["identity"]; + settings: RedditSettings = new RedditSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("response_type", "code"); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - Authorization: `Basic ${Buffer.from(`${this.settings.clientId}:${this.settings.clientSecret}`).toString("base64")}`, - "Content-Type": "application/x-www-form-urlencoded", - }) - .body( - new URLSearchParams({ - grant_type: "authorization_code", - code: code, - redirect_uri: this.getRedirectUri(), - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + Authorization: `Basic ${Buffer.from(`${this.settings.clientId}:${this.settings.clientSecret}`).toString("base64")}`, + "Content-Type": "application/x-www-form-urlencoded", + }) + .body( + new URLSearchParams({ + grant_type: "authorization_code", + code: code, + redirect_uri: this.getRedirectUri(), + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.id.toString()); + const exists = await this.hasConnection(userId, userInfo.id.toString()); - if (exists) return null; + if (exists) return null; - // TODO: connection metadata + // TODO: connection metadata - return await this.createConnection({ - user_id: userId, - external_id: userInfo.id.toString(), - friend_sync: params.friend_sync, - name: userInfo.name, - verified: userInfo.has_verified_email, - type: this.id, - }); - } + return await this.createConnection({ + user_id: userId, + external_id: userInfo.id.toString(), + friend_sync: params.friend_sync, + name: userInfo.name, + verified: userInfo.has_verified_email, + type: this.id, + }); + } } diff --git a/src/connections/Spotify/SpotifySettings.ts b/src/connections/Spotify/SpotifySettings.ts index 0f5ae95..e2db486 100644 --- a/src/connections/Spotify/SpotifySettings.ts +++ b/src/connections/Spotify/SpotifySettings.ts @@ -17,7 +17,7 @@ */ export class SpotifySettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/Spotify/index.ts b/src/connections/Spotify/index.ts index 42722fd..e1381cf 100644 --- a/src/connections/Spotify/index.ts +++ b/src/connections/Spotify/index.ts @@ -22,148 +22,148 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; export interface UserResponse { - display_name: string; - id: string; + display_name: string; + id: string; } export interface TokenErrorResponse { - error: string; - error_description: string; + error: string; + error_description: string; } export interface ErrorResponse { - error: { - status: number; - message: string; - }; + error: { + status: number; + message: string; + }; } export default class SpotifyConnection extends RefreshableConnection { - public readonly id = "spotify"; - public readonly authorizeUrl = "https://accounts.spotify.com/authorize"; - public readonly tokenUrl = "https://accounts.spotify.com/api/token"; - public readonly userInfoUrl = "https://api.spotify.com/v1/me"; - public readonly scopes = ["user-read-private", "user-read-playback-state", "user-modify-playback-state", "user-read-currently-playing"]; - settings: SpotifySettings = new SpotifySettings(); + public readonly id = "spotify"; + public readonly authorizeUrl = "https://accounts.spotify.com/authorize"; + public readonly tokenUrl = "https://accounts.spotify.com/api/token"; + public readonly userInfoUrl = "https://api.spotify.com/v1/me"; + public readonly scopes = ["user-read-private", "user-read-playback-state", "user-modify-playback-state", "user-read-currently-playing"]; + settings: SpotifySettings = new SpotifySettings(); - init(): void { - /** - * The way Discord shows the currently playing song is by using Spotifys partner API. This is obviously not possible for us. - * So to prevent spamming the spotify api we disable the ability to refresh. - */ - this.refreshEnabled = false; + init(): void { + /** + * The way Discord shows the currently playing song is by using Spotifys partner API. This is obviously not possible for us. + * So to prevent spamming the spotify api we disable the ability to refresh. + */ + this.refreshEnabled = false; - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("response_type", "code"); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, - }) - .body( - new URLSearchParams({ - grant_type: "authorization_code", - code: code, - redirect_uri: this.getRedirectUri(), - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, + }) + .body( + new URLSearchParams({ + grant_type: "authorization_code", + code: code, + redirect_uri: this.getRedirectUri(), + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async refreshToken(connectedAccount: ConnectedAccount): Promise { - if (!connectedAccount.token_data?.refresh_token) throw new Error("No refresh token available."); - const refresh_token = connectedAccount.token_data.refresh_token; - const url = this.getTokenUrl(); + async refreshToken(connectedAccount: ConnectedAccount): Promise { + if (!connectedAccount.token_data?.refresh_token) throw new Error("No refresh token available."); + const refresh_token = connectedAccount.token_data.refresh_token; + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, - }) - .body( - new URLSearchParams({ - grant_type: "refresh_token", - refresh_token, - }), - ) - .post() - .unauthorized(async () => { - // assume the token was revoked - await connectedAccount.revoke(); - return DiscordApiErrors.CONNECTION_REVOKED; - }) - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, + }) + .body( + new URLSearchParams({ + grant_type: "refresh_token", + refresh_token, + }), + ) + .post() + .unauthorized(async () => { + // assume the token was revoked + await connectedAccount.revoke(); + return DiscordApiErrors.CONNECTION_REVOKED; + }) + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.id); + const exists = await this.hasConnection(userId, userInfo.id); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - token_data: { ...tokenData, fetched_at: Date.now() }, - user_id: userId, - external_id: userInfo.id, - friend_sync: params.friend_sync, - name: userInfo.display_name, - type: this.id, - }); - } + return await this.createConnection({ + token_data: { ...tokenData, fetched_at: Date.now() }, + user_id: userId, + external_id: userInfo.id, + friend_sync: params.friend_sync, + name: userInfo.display_name, + type: this.id, + }); + } } diff --git a/src/connections/Twitch/TwitchSettings.ts b/src/connections/Twitch/TwitchSettings.ts index 31927e3..fcd0a0d 100644 --- a/src/connections/Twitch/TwitchSettings.ts +++ b/src/connections/Twitch/TwitchSettings.ts @@ -17,7 +17,7 @@ */ export class TwitchSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/Twitch/index.ts b/src/connections/Twitch/index.ts index a33cc8d..84e8b3c 100644 --- a/src/connections/Twitch/index.ts +++ b/src/connections/Twitch/index.ts @@ -22,144 +22,144 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; interface TwitchConnectionUserResponse { - data: { - id: string; - login: string; - display_name: string; - type: string; - broadcaster_type: string; - description: string; - profile_image_url: string; - offline_image_url: string; - view_count: number; - created_at: string; - }[]; + data: { + id: string; + login: string; + display_name: string; + type: string; + broadcaster_type: string; + description: string; + profile_image_url: string; + offline_image_url: string; + view_count: number; + created_at: string; + }[]; } export default class TwitchConnection extends RefreshableConnection { - public readonly id = "twitch"; - public readonly authorizeUrl = "https://id.twitch.tv/oauth2/authorize"; - public readonly tokenUrl = "https://id.twitch.tv/oauth2/token"; - public readonly userInfoUrl = "https://api.twitch.tv/helix/users"; - public readonly scopes = ["channel_subscriptions", "channel_check_subscription", "channel:read:subscriptions"]; - settings: TwitchSettings = new TwitchSettings(); + public readonly id = "twitch"; + public readonly authorizeUrl = "https://id.twitch.tv/oauth2/authorize"; + public readonly tokenUrl = "https://id.twitch.tv/oauth2/token"; + public readonly userInfoUrl = "https://api.twitch.tv/helix/users"; + public readonly scopes = ["channel_subscriptions", "channel_check_subscription", "channel:read:subscriptions"]; + settings: TwitchSettings = new TwitchSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("response_type", "code"); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - }) - .body( - new URLSearchParams({ - grant_type: "authorization_code", - code: code, - client_id: this.settings.clientId as string, - client_secret: this.settings.clientSecret as string, - redirect_uri: this.getRedirectUri(), - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + }) + .body( + new URLSearchParams({ + grant_type: "authorization_code", + code: code, + client_id: this.settings.clientId as string, + client_secret: this.settings.clientSecret as string, + redirect_uri: this.getRedirectUri(), + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async refreshToken(connectedAccount: ConnectedAccount): Promise { - if (!connectedAccount.token_data?.refresh_token) throw new Error("No refresh token available."); - const refresh_token = connectedAccount.token_data.refresh_token; + async refreshToken(connectedAccount: ConnectedAccount): Promise { + if (!connectedAccount.token_data?.refresh_token) throw new Error("No refresh token available."); + const refresh_token = connectedAccount.token_data.refresh_token; - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - }) - .body( - new URLSearchParams({ - grant_type: "refresh_token", - client_id: this.settings.clientId as string, - client_secret: this.settings.clientSecret as string, - refresh_token: refresh_token, - }), - ) - .post() - .unauthorized(async () => { - // assume the token was revoked - await connectedAccount.revoke(); - return DiscordApiErrors.CONNECTION_REVOKED; - }) - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + }) + .body( + new URLSearchParams({ + grant_type: "refresh_token", + client_id: this.settings.clientId as string, + client_secret: this.settings.clientSecret as string, + refresh_token: refresh_token, + }), + ) + .post() + .unauthorized(async () => { + // assume the token was revoked + await connectedAccount.revoke(); + return DiscordApiErrors.CONNECTION_REVOKED; + }) + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - "Client-Id": this.settings.clientId as string, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + "Client-Id": this.settings.clientId as string, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.data[0].id); + const exists = await this.hasConnection(userId, userInfo.data[0].id); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - token_data: { ...tokenData, fetched_at: Date.now() }, - user_id: userId, - external_id: userInfo.data[0].id, - friend_sync: params.friend_sync, - name: userInfo.data[0].display_name, - type: this.id, - }); - } + return await this.createConnection({ + token_data: { ...tokenData, fetched_at: Date.now() }, + user_id: userId, + external_id: userInfo.data[0].id, + friend_sync: params.friend_sync, + name: userInfo.data[0].display_name, + type: this.id, + }); + } } diff --git a/src/connections/Twitter/TwitterSettings.ts b/src/connections/Twitter/TwitterSettings.ts index df979d5..f9b2f5a 100644 --- a/src/connections/Twitter/TwitterSettings.ts +++ b/src/connections/Twitter/TwitterSettings.ts @@ -17,7 +17,7 @@ */ export class TwitterSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/Twitter/index.ts b/src/connections/Twitter/index.ts index 9bcda51..46b9289 100644 --- a/src/connections/Twitter/index.ts +++ b/src/connections/Twitter/index.ts @@ -22,16 +22,16 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; interface TwitterUserResponse { - data: { - id: string; - name: string; - username: string; - created_at: string; - location: string; - url: string; - description: string; - verified: string; - }; + data: { + id: string; + name: string; + username: string; + created_at: string; + location: string; + url: string; + description: string; + verified: string; + }; } // interface TwitterErrorResponse { @@ -40,127 +40,127 @@ // } export default class TwitterConnection extends RefreshableConnection { - public readonly id = "twitter"; - public readonly authorizeUrl = "https://twitter.com/i/oauth2/authorize"; - public readonly tokenUrl = "https://api.twitter.com/2/oauth2/token"; - public readonly userInfoUrl = "https://api.twitter.com/2/users/me?user.fields=created_at%2Cdescription%2Cid%2Cname%2Cusername%2Cverified%2Clocation%2Curl"; - public readonly scopes = ["users.read", "tweet.read"]; - settings: TwitterSettings = new TwitterSettings(); + public readonly id = "twitter"; + public readonly authorizeUrl = "https://twitter.com/i/oauth2/authorize"; + public readonly tokenUrl = "https://api.twitter.com/2/oauth2/token"; + public readonly userInfoUrl = "https://api.twitter.com/2/users/me?user.fields=created_at%2Cdescription%2Cid%2Cname%2Cusername%2Cverified%2Clocation%2Curl"; + public readonly scopes = ["users.read", "tweet.read"]; + settings: TwitterSettings = new TwitterSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("response_type", "code"); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - url.searchParams.append("code_challenge", "challenge"); // TODO: properly use PKCE challenge - url.searchParams.append("code_challenge_method", "plain"); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + url.searchParams.append("code_challenge", "challenge"); // TODO: properly use PKCE challenge + url.searchParams.append("code_challenge_method", "plain"); + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, - }) - .body( - new URLSearchParams({ - grant_type: "authorization_code", - code: code, - client_id: this.settings.clientId as string, - redirect_uri: this.getRedirectUri(), - code_verifier: "challenge", // TODO: properly use PKCE challenge - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, + }) + .body( + new URLSearchParams({ + grant_type: "authorization_code", + code: code, + client_id: this.settings.clientId as string, + redirect_uri: this.getRedirectUri(), + code_verifier: "challenge", // TODO: properly use PKCE challenge + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async refreshToken(connectedAccount: ConnectedAccount): Promise { - if (!connectedAccount.token_data?.refresh_token) throw new Error("No refresh token available."); - const refresh_token = connectedAccount.token_data.refresh_token; + async refreshToken(connectedAccount: ConnectedAccount): Promise { + if (!connectedAccount.token_data?.refresh_token) throw new Error("No refresh token available."); + const refresh_token = connectedAccount.token_data.refresh_token; - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, - }) - .body( - new URLSearchParams({ - grant_type: "refresh_token", - refresh_token, - client_id: this.settings.clientId as string, - redirect_uri: this.getRedirectUri(), - code_verifier: "challenge", // TODO: properly use PKCE challenge - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, + }) + .body( + new URLSearchParams({ + grant_type: "refresh_token", + refresh_token, + client_id: this.settings.clientId as string, + redirect_uri: this.getRedirectUri(), + code_verifier: "challenge", // TODO: properly use PKCE challenge + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.data.id); + const exists = await this.hasConnection(userId, userInfo.data.id); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - token_data: { ...tokenData, fetched_at: Date.now() }, - user_id: userId, - external_id: userInfo.data.id, - friend_sync: params.friend_sync, - name: userInfo.data.name, - type: this.id, - }); - } + return await this.createConnection({ + token_data: { ...tokenData, fetched_at: Date.now() }, + user_id: userId, + external_id: userInfo.data.id, + friend_sync: params.friend_sync, + name: userInfo.data.name, + type: this.id, + }); + } } diff --git a/src/connections/Xbox/XboxSettings.ts b/src/connections/Xbox/XboxSettings.ts index 5dc764d..b7e97de 100644 --- a/src/connections/Xbox/XboxSettings.ts +++ b/src/connections/Xbox/XboxSettings.ts @@ -17,7 +17,7 @@ */ export class XboxSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/Xbox/index.ts b/src/connections/Xbox/index.ts index 37423f9..184fe76 100644 --- a/src/connections/Xbox/index.ts +++ b/src/connections/Xbox/index.ts @@ -22,20 +22,20 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; interface XboxUserResponse { - IssueInstant: string; - NotAfter: string; - Token: string; - DisplayClaims: { - xui: { - gtg: string; - xid: string; - uhs: string; - agg: string; - usr: string; - utr: string; - prv: string; - }[]; - }; + IssueInstant: string; + NotAfter: string; + Token: string; + DisplayClaims: { + xui: { + gtg: string; + xid: string; + uhs: string; + agg: string; + usr: string; + utr: string; + prv: string; + }[]; + }; } // interface XboxErrorResponse { @@ -44,138 +44,138 @@ // } export default class XboxConnection extends Connection { - public readonly id = "xbox"; - public readonly authorizeUrl = "https://login.live.com/oauth20_authorize.srf"; - public readonly tokenUrl = "https://login.live.com/oauth20_token.srf"; - public readonly userInfoUrl = "https://xsts.auth.xboxlive.com/xsts/authorize"; - public readonly userAuthUrl = "https://user.auth.xboxlive.com/user/authenticate"; - public readonly scopes = ["Xboxlive.signin", "Xboxlive.offline_access"]; - settings: XboxSettings = new XboxSettings(); + public readonly id = "xbox"; + public readonly authorizeUrl = "https://login.live.com/oauth20_authorize.srf"; + public readonly tokenUrl = "https://login.live.com/oauth20_token.srf"; + public readonly userInfoUrl = "https://xsts.auth.xboxlive.com/xsts/authorize"; + public readonly userAuthUrl = "https://user.auth.xboxlive.com/user/authenticate"; + public readonly scopes = ["Xboxlive.signin", "Xboxlive.offline_access"]; + settings: XboxSettings = new XboxSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("response_type", "code"); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - url.searchParams.append("approval_prompt", "auto"); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + url.searchParams.append("approval_prompt", "auto"); + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async getUserToken(token: string): Promise { - return wretch(this.userAuthUrl) - .headers({ - "x-xbl-contract-version": "3", - "Content-Type": "application/json", - Accept: "application/json", - }) - .body( - JSON.stringify({ - RelyingParty: "http://auth.xboxlive.com", - TokenType: "JWT", - Properties: { - AuthMethod: "RPS", - SiteName: "user.auth.xboxlive.com", - RpsTicket: `d=${token}`, - }, - }), - ) - .post() - .json((res: XboxUserResponse) => res.Token) - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + async getUserToken(token: string): Promise { + return wretch(this.userAuthUrl) + .headers({ + "x-xbl-contract-version": "3", + "Content-Type": "application/json", + Accept: "application/json", + }) + .body( + JSON.stringify({ + RelyingParty: "http://auth.xboxlive.com", + TokenType: "JWT", + Properties: { + AuthMethod: "RPS", + SiteName: "user.auth.xboxlive.com", + RpsTicket: `d=${token}`, + }, + }), + ) + .post() + .json((res: XboxUserResponse) => res.Token) + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, - }) - .body( - new URLSearchParams({ - grant_type: "authorization_code", - code: code, - client_id: this.settings.clientId as string, - redirect_uri: this.getRedirectUri(), - scope: this.scopes.join(" "), - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + Authorization: `Basic ${Buffer.from(`${this.settings.clientId as string}:${this.settings.clientSecret as string}`).toString("base64")}`, + }) + .body( + new URLSearchParams({ + grant_type: "authorization_code", + code: code, + client_id: this.settings.clientId as string, + redirect_uri: this.getRedirectUri(), + scope: this.scopes.join(" "), + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - "x-xbl-contract-version": "3", - "Content-Type": "application/json", - Accept: "application/json", - }) - .body( - JSON.stringify({ - RelyingParty: "http://xboxlive.com", - TokenType: "JWT", - Properties: { - UserTokens: [token], - SandboxId: "RETAIL", - }, - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + "x-xbl-contract-version": "3", + "Content-Type": "application/json", + Accept: "application/json", + }) + .body( + JSON.stringify({ + RelyingParty: "http://xboxlive.com", + TokenType: "JWT", + Properties: { + UserTokens: [token], + SandboxId: "RETAIL", + }, + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userToken = await this.getUserToken(tokenData.access_token); - const userInfo = await this.getUser(userToken); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userToken = await this.getUserToken(tokenData.access_token); + const userInfo = await this.getUser(userToken); - const exists = await this.hasConnection(userId, userInfo.DisplayClaims.xui[0].xid); + const exists = await this.hasConnection(userId, userInfo.DisplayClaims.xui[0].xid); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - token_data: { ...tokenData, fetched_at: Date.now() }, - user_id: userId, - external_id: userInfo.DisplayClaims.xui[0].xid, - friend_sync: params.friend_sync, - name: userInfo.DisplayClaims.xui[0].gtg, - type: this.id, - }); - } + return await this.createConnection({ + token_data: { ...tokenData, fetched_at: Date.now() }, + user_id: userId, + external_id: userInfo.DisplayClaims.xui[0].xid, + friend_sync: params.friend_sync, + name: userInfo.DisplayClaims.xui[0].gtg, + type: this.id, + }); + } } diff --git a/src/connections/Youtube/YoutubeSettings.ts b/src/connections/Youtube/YoutubeSettings.ts index f2daaad..b2f7c1b 100644 --- a/src/connections/Youtube/YoutubeSettings.ts +++ b/src/connections/Youtube/YoutubeSettings.ts @@ -17,7 +17,7 @@ */ export class YoutubeSettings { - enabled: boolean = false; - clientId: string | null = null; - clientSecret: string | null = null; + enabled: boolean = false; + clientId: string | null = null; + clientSecret: string | null = null; } diff --git a/src/connections/Youtube/index.ts b/src/connections/Youtube/index.ts index 7c5dafe..de452df 100644 --- a/src/connections/Youtube/index.ts +++ b/src/connections/Youtube/index.ts @@ -22,117 +22,117 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; interface YouTubeConnectionChannelListResult { - items: { - snippet: { - // thumbnails: Thumbnails; - title: string; - country: string; - publishedAt: string; - // localized: Localized; - description: string; - }; - kind: string; - etag: string; - id: string; - }[]; - kind: string; - etag: string; - pageInfo: { - resultsPerPage: number; - totalResults: number; - }; + items: { + snippet: { + // thumbnails: Thumbnails; + title: string; + country: string; + publishedAt: string; + // localized: Localized; + description: string; + }; + kind: string; + etag: string; + id: string; + }[]; + kind: string; + etag: string; + pageInfo: { + resultsPerPage: number; + totalResults: number; + }; } export default class YoutubeConnection extends Connection { - public readonly id = "youtube"; - public readonly authorizeUrl = "https://accounts.google.com/o/oauth2/v2/auth"; - public readonly tokenUrl = "https://oauth2.googleapis.com/token"; - public readonly userInfoUrl = "https://www.googleapis.com/youtube/v3/channels?mine=true&part=snippet"; - public readonly scopes = ["https://www.googleapis.com/auth/youtube.readonly"]; - settings: YoutubeSettings = new YoutubeSettings(); + public readonly id = "youtube"; + public readonly authorizeUrl = "https://accounts.google.com/o/oauth2/v2/auth"; + public readonly tokenUrl = "https://oauth2.googleapis.com/token"; + public readonly userInfoUrl = "https://www.googleapis.com/youtube/v3/channels?mine=true&part=snippet"; + public readonly scopes = ["https://www.googleapis.com/auth/youtube.readonly"]; + settings: YoutubeSettings = new YoutubeSettings(); - init(): void { - this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); + init(): void { + this.settings = ConnectionLoader.getConnectionConfig(this.id, this.settings); - if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); - } + if (this.settings.enabled && (!this.settings.clientId || !this.settings.clientSecret)) throw new Error(`Invalid settings for connection ${this.id}`); + } - getAuthorizationUrl(userId: string): string { - const state = this.createState(userId); - const url = new URL(this.authorizeUrl); + getAuthorizationUrl(userId: string): string { + const state = this.createState(userId); + const url = new URL(this.authorizeUrl); - url.searchParams.append("client_id", this.settings.clientId as string); - url.searchParams.append("redirect_uri", this.getRedirectUri()); - url.searchParams.append("response_type", "code"); - url.searchParams.append("scope", this.scopes.join(" ")); - url.searchParams.append("state", state); - return url.toString(); - } + url.searchParams.append("client_id", this.settings.clientId as string); + url.searchParams.append("redirect_uri", this.getRedirectUri()); + url.searchParams.append("response_type", "code"); + url.searchParams.append("scope", this.scopes.join(" ")); + url.searchParams.append("state", state); + return url.toString(); + } - getTokenUrl(): string { - return this.tokenUrl; - } + getTokenUrl(): string { + return this.tokenUrl; + } - async exchangeCode(state: string, code: string): Promise { - this.validateState(state); + async exchangeCode(state: string, code: string): Promise { + this.validateState(state); - const url = this.getTokenUrl(); + const url = this.getTokenUrl(); - return wretch(url.toString()) - .headers({ - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded", - }) - .body( - new URLSearchParams({ - grant_type: "authorization_code", - code: code, - client_id: this.settings.clientId as string, - client_secret: this.settings.clientSecret as string, - redirect_uri: this.getRedirectUri(), - }), - ) - .post() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + return wretch(url.toString()) + .headers({ + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + }) + .body( + new URLSearchParams({ + grant_type: "authorization_code", + code: code, + client_id: this.settings.clientId as string, + client_secret: this.settings.clientSecret as string, + redirect_uri: this.getRedirectUri(), + }), + ) + .post() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async getUser(token: string): Promise { - const url = new URL(this.userInfoUrl); - return wretch(url.toString()) - .headers({ - Authorization: `Bearer ${token}`, - }) - .get() - .json() - .catch((e) => { - console.error(e); - throw DiscordApiErrors.GENERAL_ERROR; - }); - } + async getUser(token: string): Promise { + const url = new URL(this.userInfoUrl); + return wretch(url.toString()) + .headers({ + Authorization: `Bearer ${token}`, + }) + .get() + .json() + .catch((e) => { + console.error(e); + throw DiscordApiErrors.GENERAL_ERROR; + }); + } - async handleCallback(params: ConnectionCallbackSchema): Promise { - const { state, code } = params; - if (!code) throw new Error("No code provided"); + async handleCallback(params: ConnectionCallbackSchema): Promise { + const { state, code } = params; + if (!code) throw new Error("No code provided"); - const userId = this.getUserId(state); - const tokenData = await this.exchangeCode(state, code); - const userInfo = await this.getUser(tokenData.access_token); + const userId = this.getUserId(state); + const tokenData = await this.exchangeCode(state, code); + const userInfo = await this.getUser(tokenData.access_token); - const exists = await this.hasConnection(userId, userInfo.items[0].id); + const exists = await this.hasConnection(userId, userInfo.items[0].id); - if (exists) return null; + if (exists) return null; - return await this.createConnection({ - token_data: { ...tokenData, fetched_at: Date.now() }, - user_id: userId, - external_id: userInfo.items[0].id, - friend_sync: params.friend_sync, - name: userInfo.items[0].snippet.title, - type: this.id, - }); - } + return await this.createConnection({ + token_data: { ...tokenData, fetched_at: Date.now() }, + user_id: userId, + external_id: userInfo.items[0].id, + friend_sync: params.friend_sync, + name: userInfo.items[0].snippet.title, + type: this.id, + }); + } } diff --git a/src/gateway/Server.ts b/src/gateway/Server.ts index 9baba50..786d173 100644 --- a/src/gateway/Server.ts +++ b/src/gateway/Server.ts @@ -26,59 +26,59 @@ import { randomString } from "@spacebar/api"; export class Server { - public ws: ws.Server; - public port: number; - public server: http.Server; - public production: boolean; + public ws: ws.Server; + public port: number; + public server: http.Server; + public production: boolean; - constructor({ port, server, production }: { port: number; server?: http.Server; production?: boolean }) { - this.port = port; - this.production = production || false; + constructor({ port, server, production }: { port: number; server?: http.Server; production?: boolean }) { + this.port = port; + this.production = production || false; - if (server) this.server = server; - else { - this.server = http.createServer(function (req, res) { - if (!req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) { - res.setHeader("Set-Cookie", `__sb_sessid=${randomString(32)}; Secure; HttpOnly; SameSite=None; Path=/`); - } + if (server) this.server = server; + else { + this.server = http.createServer(function (req, res) { + if (!req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) { + res.setHeader("Set-Cookie", `__sb_sessid=${randomString(32)}; Secure; HttpOnly; SameSite=None; Path=/`); + } - res.writeHead(200).end("Online"); - }); - } + res.writeHead(200).end("Online"); + }); + } - this.server.on("upgrade", (request, socket, head) => { - this.ws.handleUpgrade(request, socket, head, (socket) => { - this.ws.emit("connection", socket, request); - }); - }); + this.server.on("upgrade", (request, socket, head) => { + this.ws.handleUpgrade(request, socket, head, (socket) => { + this.ws.emit("connection", socket, request); + }); + }); - this.ws = new ws.Server({ - maxPayload: 4096, - noServer: true, - }); - this.ws.on("connection", Connection); - this.ws.on("error", console.error); - } + this.ws = new ws.Server({ + maxPayload: 4096, + noServer: true, + }); + this.ws.on("connection", Connection); + this.ws.on("error", console.error); + } - async start(): Promise { - await initDatabase(); - await Config.init(); - await initEvent(); - // temporary fix - await cleanupOnStartup(); + async start(): Promise { + await initDatabase(); + await Config.init(); + await initEvent(); + // temporary fix + await cleanupOnStartup(); - if (!this.server.listening) { - this.server.listen(this.port); - console.log(`[Gateway] online on 0.0.0.0:${this.port}`); - } - } + if (!this.server.listening) { + this.server.listen(this.port); + console.log(`[Gateway] online on 0.0.0.0:${this.port}`); + } + } - async stop() { - this.ws.clients.forEach((x) => x.close()); - this.ws.close(() => { - this.server.close(() => { - closeDatabase(); - }); - }); - } + async stop() { + this.ws.clients.forEach((x) => x.close()); + this.ws.close(() => { + this.server.close(() => { + closeDatabase(); + }); + }); + } } diff --git a/src/gateway/events/Close.ts b/src/gateway/events/Close.ts index 3c66409..988f17f 100644 --- a/src/gateway/events/Close.ts +++ b/src/gateway/events/Close.ts @@ -20,78 +20,78 @@ import { emitEvent, PresenceUpdateEvent, PrivateSessionProjection, Session, SessionsReplace, User, VoiceState, VoiceStateUpdateEvent } from "@spacebar/util"; export async function Close(this: WebSocket, code: number, reason: Buffer) { - console.log("[WebSocket] closed", code, reason.toString()); - if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); - if (this.readyTimeout) clearTimeout(this.readyTimeout); - this.deflate?.close(); - this.inflate?.close(); - this.removeAllListeners(); + console.log("[WebSocket] closed", code, reason.toString()); + if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); + if (this.readyTimeout) clearTimeout(this.readyTimeout); + this.deflate?.close(); + this.inflate?.close(); + this.removeAllListeners(); - if (this.session_id) { - await Session.delete({ session_id: this.session_id }); + if (this.session_id) { + await Session.delete({ session_id: this.session_id }); - const voiceState = await VoiceState.findOne({ - where: { user_id: this.user_id }, - }); + const voiceState = await VoiceState.findOne({ + where: { user_id: this.user_id }, + }); - // clear the voice state for this session if user was in voice channel - if (voiceState && voiceState.session_id === this.session_id && voiceState.channel_id) { - const prevGuildId = voiceState.guild_id; - const prevChannelId = voiceState.channel_id; + // clear the voice state for this session if user was in voice channel + if (voiceState && voiceState.session_id === this.session_id && voiceState.channel_id) { + const prevGuildId = voiceState.guild_id; + const prevChannelId = voiceState.channel_id; - // @ts-expect-error channel_id is nullable - voiceState.channel_id = null; - // @ts-expect-error guild_id is nullable - voiceState.guild_id = null; - voiceState.self_stream = false; - voiceState.self_video = false; - await voiceState.save(); + // @ts-expect-error channel_id is nullable + voiceState.channel_id = null; + // @ts-expect-error guild_id is nullable + voiceState.guild_id = null; + voiceState.self_stream = false; + voiceState.self_video = false; + await voiceState.save(); - // let the users in previous guild/channel know that user disconnected - await emitEvent({ - event: "VOICE_STATE_UPDATE", - data: { - ...voiceState.toPublicVoiceState(), - guild_id: prevGuildId, // have to send the previous guild_id because that's what client expects for disconnect messages - }, - guild_id: prevGuildId, - channel_id: prevChannelId, - } as VoiceStateUpdateEvent); - } - } + // let the users in previous guild/channel know that user disconnected + await emitEvent({ + event: "VOICE_STATE_UPDATE", + data: { + ...voiceState.toPublicVoiceState(), + guild_id: prevGuildId, // have to send the previous guild_id because that's what client expects for disconnect messages + }, + guild_id: prevGuildId, + channel_id: prevChannelId, + } as VoiceStateUpdateEvent); + } + } - if (this.user_id) { - const sessions = await Session.find({ - where: { user_id: this.user_id }, - }); - await emitEvent({ - event: "SESSIONS_REPLACE", - user_id: this.user_id, - data: sessions.map((x) => x.toPrivateGatewayDeviceInfo()), - } as SessionsReplace); - const session = sessions[0] || { - activities: [], - client_status: {}, - status: "offline", - }; + if (this.user_id) { + const sessions = await Session.find({ + where: { user_id: this.user_id }, + }); + await emitEvent({ + event: "SESSIONS_REPLACE", + user_id: this.user_id, + data: sessions.map((x) => x.toPrivateGatewayDeviceInfo()), + } as SessionsReplace); + const session = sessions[0] || { + activities: [], + client_status: {}, + status: "offline", + }; - // TODO - // If a user was deleted, they may still be connected to gateway, - // which will cause this to throw when they disconnect. - // just send the ID of the user instead of the full correct payload for now - const userOrId = await User.getPublicUser(this.user_id).catch(() => ({ - id: this.user_id, - })); + // TODO + // If a user was deleted, they may still be connected to gateway, + // which will cause this to throw when they disconnect. + // just send the ID of the user instead of the full correct payload for now + const userOrId = await User.getPublicUser(this.user_id).catch(() => ({ + id: this.user_id, + })); - await emitEvent({ - event: "PRESENCE_UPDATE", - user_id: this.user_id, - data: { - user: userOrId, - activities: session.activities, - client_status: session?.client_status, - status: session.getPublicStatus?.() ?? session.status, - }, - } as PresenceUpdateEvent); - } + await emitEvent({ + event: "PRESENCE_UPDATE", + user_id: this.user_id, + data: { + user: userOrId, + activities: session.activities, + client_status: session?.client_status, + status: session.getPublicStatus?.() ?? session.status, + }, + } as PresenceUpdateEvent); + } } diff --git a/src/gateway/events/Connection.ts b/src/gateway/events/Connection.ts index 37e2eba..f94484a 100644 --- a/src/gateway/events/Connection.ts +++ b/src/gateway/events/Connection.ts @@ -33,9 +33,9 @@ let erlpack: ErlpackType | null = null; try { - erlpack = require("@yukikaze-bot/erlpack") as ErlpackType; + erlpack = require("@yukikaze-bot/erlpack") as ErlpackType; } catch (e) { - console.log("Failed to import @yukikaze-bot/erlpack: ", e); + console.log("Failed to import @yukikaze-bot/erlpack: ", e); } // TODO: check rate limit @@ -43,98 +43,98 @@ // TODO: check msg max size export async function Connection(this: WS.Server, socket: WebSocket, request: IncomingMessage) { - const forwardedFor = Config.get().security.forwardedFor; - const ipAddress = forwardedFor ? (request.headers[forwardedFor.toLowerCase()] as string) : request.socket.remoteAddress; + const forwardedFor = Config.get().security.forwardedFor; + const ipAddress = forwardedFor ? (request.headers[forwardedFor.toLowerCase()] as string) : request.socket.remoteAddress; - socket.ipAddress = ipAddress; - socket.userAgent = request.headers["user-agent"] as string; + socket.ipAddress = ipAddress; + socket.userAgent = request.headers["user-agent"] as string; - if (!ipAddress && Config.get().security.cdnSignatureIncludeIp) { - return socket.close(CLOSECODES.Decode_error, "Gateway connection rejected: IP address is required."); - } + if (!ipAddress && Config.get().security.cdnSignatureIncludeIp) { + return socket.close(CLOSECODES.Decode_error, "Gateway connection rejected: IP address is required."); + } - if (!socket.userAgent && Config.get().security.cdnSignatureIncludeUserAgent) { - return socket.close(CLOSECODES.Decode_error, "Gateway connection rejected: User-Agent header is required."); - } + if (!socket.userAgent && Config.get().security.cdnSignatureIncludeUserAgent) { + return socket.close(CLOSECODES.Decode_error, "Gateway connection rejected: User-Agent header is required."); + } - if (request.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) { - socket.fingerprint = request.headers.cookie - .split("; ") - .find((x) => x.startsWith("__sb_sessid=")) - ?.split("=")[1]; - } + if (request.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) { + socket.fingerprint = request.headers.cookie + .split("; ") + .find((x) => x.startsWith("__sb_sessid=")) + ?.split("=")[1]; + } - //Create session ID when the connection is opened. This allows gateway dump to group the initial websocket messages with the rest of the conversation. - const session_id = "TEMP_" + genSessionId(); - socket.session_id = session_id; //Set the session of the WebSocket object + //Create session ID when the connection is opened. This allows gateway dump to group the initial websocket messages with the rest of the conversation. + const session_id = "TEMP_" + genSessionId(); + socket.session_id = session_id; //Set the session of the WebSocket object - try { - // @ts-ignore - socket.on("close", Close); - // @ts-ignore - socket.on("message", Message); + try { + // @ts-ignore + socket.on("close", Close); + // @ts-ignore + socket.on("message", Message); - socket.on("error", (err) => console.error("[Gateway]", err)); + socket.on("error", (err) => console.error("[Gateway]", err)); - console.log(`[Gateway] New connection from ${ipAddress}, total ${this.clients.size}`); + console.log(`[Gateway] New connection from ${ipAddress}, total ${this.clients.size}`); - if (process.env.WS_LOGEVENTS) - [ - "close", - "error", - "upgrade", - //"message", - "open", - "ping", - "pong", - "unexpected-response", - ].forEach((x) => { - socket.on(x, (y) => console.log(x, y)); - }); + if (process.env.WS_LOGEVENTS) + [ + "close", + "error", + "upgrade", + //"message", + "open", + "ping", + "pong", + "unexpected-response", + ].forEach((x) => { + socket.on(x, (y) => console.log(x, y)); + }); - const { searchParams } = new URL(`http://localhost${request.url}`); - // @ts-ignore - socket.encoding = searchParams.get("encoding") || "json"; - if (!["json", "etf"].includes(socket.encoding)) return socket.close(CLOSECODES.Decode_error); + const { searchParams } = new URL(`http://localhost${request.url}`); + // @ts-ignore + socket.encoding = searchParams.get("encoding") || "json"; + if (!["json", "etf"].includes(socket.encoding)) return socket.close(CLOSECODES.Decode_error); - if (socket.encoding === "etf" && !erlpack) throw new Error("Erlpack is not installed: 'npm i @yukikaze-bot/erlpack'"); + if (socket.encoding === "etf" && !erlpack) throw new Error("Erlpack is not installed: 'npm i @yukikaze-bot/erlpack'"); - socket.version = Number(searchParams.get("version")) || 8; - if (socket.version != 8) return socket.close(CLOSECODES.Invalid_API_version); + socket.version = Number(searchParams.get("version")) || 8; + if (socket.version != 8) return socket.close(CLOSECODES.Invalid_API_version); - // @ts-ignore - socket.compress = searchParams.get("compress") || ""; - if (socket.compress) { - if (socket.compress === "zlib-stream") { - socket.deflate = new Deflate(); - socket.inflate = new Inflate(); - } else if (socket.compress === "zstd-stream") { - socket.zstdEncoder = new Encoder(6); - socket.zstdDecoder = new Decoder(); - } else { - return socket.close(CLOSECODES.Decode_error); - } - } + // @ts-ignore + socket.compress = searchParams.get("compress") || ""; + if (socket.compress) { + if (socket.compress === "zlib-stream") { + socket.deflate = new Deflate(); + socket.inflate = new Inflate(); + } else if (socket.compress === "zstd-stream") { + socket.zstdEncoder = new Encoder(6); + socket.zstdDecoder = new Decoder(); + } else { + return socket.close(CLOSECODES.Decode_error); + } + } - socket.events = {}; - socket.member_events = {}; - socket.permissions = {}; - socket.sequence = 0; + socket.events = {}; + socket.member_events = {}; + socket.permissions = {}; + socket.sequence = 0; - setHeartbeat(socket); + setHeartbeat(socket); - await Send(socket, { - op: OPCODES.Hello, - d: { - heartbeat_interval: 1000 * 30, - }, - }); + await Send(socket, { + op: OPCODES.Hello, + d: { + heartbeat_interval: 1000 * 30, + }, + }); - socket.readyTimeout = setTimeout(() => { - return socket.close(CLOSECODES.Session_timed_out); - }, 1000 * 30); - } catch (error) { - console.error(error); - return socket.close(CLOSECODES.Unknown_error); - } + socket.readyTimeout = setTimeout(() => { + return socket.close(CLOSECODES.Session_timed_out); + }, 1000 * 30); + } catch (error) { + console.error(error); + return socket.close(CLOSECODES.Unknown_error); + } } diff --git a/src/gateway/events/Message.ts b/src/gateway/events/Message.ts index dbdad93..0ac57a3 100644 --- a/src/gateway/events/Message.ts +++ b/src/gateway/events/Message.ts @@ -30,69 +30,69 @@ let erlpack: ErlpackType | null = null; try { - erlpack = require("@yukikaze-bot/erlpack") as ErlpackType; + erlpack = require("@yukikaze-bot/erlpack") as ErlpackType; } catch (e) { - console.log("Failed to import @yukikaze-bot/erlpack: ", e); + console.log("Failed to import @yukikaze-bot/erlpack: ", e); } export async function Message(this: WebSocket, buffer: WS.Data) { - // TODO: compression - let data: Payload; + // TODO: compression + let data: Payload; - if ( - (buffer instanceof Buffer && buffer[0] === 123) || // ASCII 123 = `{`. Bad check for JSON - typeof buffer === "string" - ) { - data = bigIntJson.parse(buffer.toString()); - } else if (this.encoding === "json" && buffer instanceof Buffer) { - if (this.compress === "zlib-stream") { - try { - buffer = this.inflate!.process(buffer); - } catch { - buffer = buffer.toString(); - } - } else if (this.compress === "zstd-stream") { - try { - buffer = await this.zstdDecoder!.decode(buffer); - } catch { - buffer = buffer.toString(); - } - } - data = bigIntJson.parse(buffer as string); - } else if (this.encoding === "etf" && buffer instanceof Buffer && erlpack) { - try { - data = erlpack.unpack(buffer); - } catch { - return this.close(CLOSECODES.Decode_error); - } - } else return this.close(CLOSECODES.Decode_error); + if ( + (buffer instanceof Buffer && buffer[0] === 123) || // ASCII 123 = `{`. Bad check for JSON + typeof buffer === "string" + ) { + data = bigIntJson.parse(buffer.toString()); + } else if (this.encoding === "json" && buffer instanceof Buffer) { + if (this.compress === "zlib-stream") { + try { + buffer = this.inflate!.process(buffer); + } catch { + buffer = buffer.toString(); + } + } else if (this.compress === "zstd-stream") { + try { + buffer = await this.zstdDecoder!.decode(buffer); + } catch { + buffer = buffer.toString(); + } + } + data = bigIntJson.parse(buffer as string); + } else if (this.encoding === "etf" && buffer instanceof Buffer && erlpack) { + try { + data = erlpack.unpack(buffer); + } catch { + return this.close(CLOSECODES.Decode_error); + } + } else return this.close(CLOSECODES.Decode_error); - if (process.env.WS_VERBOSE) console.log(`[Websocket] Incomming message: ${JSON.stringify(data)}`); + if (process.env.WS_VERBOSE) console.log(`[Websocket] Incomming message: ${JSON.stringify(data)}`); - if (process.env.WS_DUMP) { - const id = this.session_id || "unknown"; + if (process.env.WS_DUMP) { + const id = this.session_id || "unknown"; - await fs.mkdir(path.join("dump", id), { recursive: true }); - await fs.writeFile(path.join("dump", id, `${Date.now()}.in.json`), JSON.stringify(data, null, 2)); + await fs.mkdir(path.join("dump", id), { recursive: true }); + await fs.writeFile(path.join("dump", id, `${Date.now()}.in.json`), JSON.stringify(data, null, 2)); - if (!this.session_id) console.log("[Gateway] Unknown session id, dumping to unknown folder"); - } + if (!this.session_id) console.log("[Gateway] Unknown session id, dumping to unknown folder"); + } - check.call(this, PayloadSchema, data); + check.call(this, PayloadSchema, data); - const OPCodeHandler = OPCodeHandlers[data.op]; - if (!OPCodeHandler) { - console.error("[Gateway] Unknown opcode " + data.op); - // TODO: if all opcodes are implemented comment this out: - // this.close(CLOSECODES.Unknown_opcode); - return; - } + const OPCodeHandler = OPCodeHandlers[data.op]; + if (!OPCodeHandler) { + console.error("[Gateway] Unknown opcode " + data.op); + // TODO: if all opcodes are implemented comment this out: + // this.close(CLOSECODES.Unknown_opcode); + return; + } - try { - return await OPCodeHandler.call(this, data); - } catch (error) { - console.error(`Error: Op ${data.op}`, error); - // if (!this.CLOSED && this.CLOSING) - return this.close(CLOSECODES.Unknown_error); - } + try { + return await OPCodeHandler.call(this, data); + } catch (error) { + console.error(`Error: Op ${data.op}`, error); + // if (!this.CLOSED && this.CLOSING) + return this.close(CLOSECODES.Unknown_error); + } } diff --git a/src/gateway/listener/listener.ts b/src/gateway/listener/listener.ts index f7e41f5..c87b5df 100644 --- a/src/gateway/listener/listener.ts +++ b/src/gateway/listener/listener.ts @@ -17,19 +17,19 @@ */ import { - getPermission, - Permissions, - RabbitMQ, - listenEvent, - EventOpts, - ListenEventOpts, - Member, - EVENTEnum, - Relationship, - Message, - NewUrlUserSignatureData, - GuildMemberAddEvent, - Ban, + getPermission, + Permissions, + RabbitMQ, + listenEvent, + EventOpts, + ListenEventOpts, + Member, + EVENTEnum, + Relationship, + Message, + NewUrlUserSignatureData, + GuildMemberAddEvent, + Ban, } from "@spacebar/util"; import { OPCODES } from "../util/Constants"; import { Send } from "../util/Send"; @@ -48,251 +48,251 @@ // https://discord.com/developers/docs/topics/gateway#sharding export function handlePresenceUpdate(this: WebSocket, { event, acknowledge, data }: EventOpts) { - acknowledge?.(); - if (event === EVENTEnum.PresenceUpdate) { - return Send(this, { - op: OPCODES.Dispatch, - t: event, - d: data, - s: this.sequence++, - }); - } + acknowledge?.(); + if (event === EVENTEnum.PresenceUpdate) { + return Send(this, { + op: OPCODES.Dispatch, + t: event, + d: data, + s: this.sequence++, + }); + } } // TODO: use already queried guilds/channels of Identify and don't fetch them again export async function setupListener(this: WebSocket) { - const [members, recipients, relationships] = await Promise.all([ - Member.find({ - where: { id: this.user_id }, - relations: ["guild", "guild.channels"], - }), - Recipient.find({ - where: { user_id: this.user_id, closed: false }, - relations: ["channel"], - }), - Relationship.find({ - where: { - from_id: this.user_id, - type: RelationshipType.friends, - }, - }), - ]); + const [members, recipients, relationships] = await Promise.all([ + Member.find({ + where: { id: this.user_id }, + relations: ["guild", "guild.channels"], + }), + Recipient.find({ + where: { user_id: this.user_id, closed: false }, + relations: ["channel"], + }), + Relationship.find({ + where: { + from_id: this.user_id, + type: RelationshipType.friends, + }, + }), + ]); - const guilds = members.map((x) => x.guild); - const dm_channels = recipients.map((x) => x.channel); + const guilds = members.map((x) => x.guild); + const dm_channels = recipients.map((x) => x.channel); - const opts: { - acknowledge: boolean; - channel?: AMQChannel & { queues?: unknown; ch?: number }; - } = { - acknowledge: true, - }; - this.listen_options = opts; - const consumer = consume.bind(this); + const opts: { + acknowledge: boolean; + channel?: AMQChannel & { queues?: unknown; ch?: number }; + } = { + acknowledge: true, + }; + this.listen_options = opts; + const consumer = consume.bind(this); - const handleChannelError = (err: unknown) => { - console.error(`[RabbitMQ] [user-${this.user_id}] Channel Error (Handled):`, err); - }; + const handleChannelError = (err: unknown) => { + console.error(`[RabbitMQ] [user-${this.user_id}] Channel Error (Handled):`, err); + }; - console.log("[RabbitMQ] setupListener: open for ", this.user_id); - if (RabbitMQ.connection) { - console.log("[RabbitMQ] setupListener: opts.channel = ", typeof opts.channel, "with channel id", opts.channel?.ch); - opts.channel = await RabbitMQ.connection.createChannel(); + console.log("[RabbitMQ] setupListener: open for ", this.user_id); + if (RabbitMQ.connection) { + console.log("[RabbitMQ] setupListener: opts.channel = ", typeof opts.channel, "with channel id", opts.channel?.ch); + opts.channel = await RabbitMQ.connection.createChannel(); - opts.channel.on("error", handleChannelError); - opts.channel.queues = {}; - console.log("[RabbitMQ] channel created: ", typeof opts.channel, "with channel id", opts.channel?.ch); - } + opts.channel.on("error", handleChannelError); + opts.channel.queues = {}; + console.log("[RabbitMQ] channel created: ", typeof opts.channel, "with channel id", opts.channel?.ch); + } - this.events[this.user_id] = await listenEvent(this.user_id, consumer, opts); + this.events[this.user_id] = await listenEvent(this.user_id, consumer, opts); - relationships.forEach(async (relationship) => { - this.events[relationship.to_id] = await listenEvent(relationship.to_id, handlePresenceUpdate.bind(this), opts); - }); + relationships.forEach(async (relationship) => { + this.events[relationship.to_id] = await listenEvent(relationship.to_id, handlePresenceUpdate.bind(this), opts); + }); - dm_channels.forEach(async (channel) => { - this.events[channel.id] = await listenEvent(channel.id, consumer, opts); - }); + dm_channels.forEach(async (channel) => { + this.events[channel.id] = await listenEvent(channel.id, consumer, opts); + }); - guilds.forEach(async (guild) => { - const permission = await getPermission(this.user_id, guild.id); - this.permissions[guild.id] = permission; - this.events[guild.id] = await listenEvent(guild.id, consumer, opts); + guilds.forEach(async (guild) => { + const permission = await getPermission(this.user_id, guild.id); + this.permissions[guild.id] = permission; + this.events[guild.id] = await listenEvent(guild.id, consumer, opts); - guild.channels.forEach(async (channel) => { - if (permission.overwriteChannel(channel.permission_overwrites ?? []).has("VIEW_CHANNEL")) { - this.events[channel.id] = await listenEvent(channel.id, consumer, opts); - } - }); - }); + guild.channels.forEach(async (channel) => { + if (permission.overwriteChannel(channel.permission_overwrites ?? []).has("VIEW_CHANNEL")) { + this.events[channel.id] = await listenEvent(channel.id, consumer, opts); + } + }); + }); - this.once("close", async () => { - console.log("[RabbitMQ] setupListener: close for", this.user_id, "=", typeof opts.channel, "with channel id", opts.channel?.ch); + this.once("close", async () => { + console.log("[RabbitMQ] setupListener: close for", this.user_id, "=", typeof opts.channel, "with channel id", opts.channel?.ch); - // wait for event consumer cancellation - await Promise.all( - Object.values(this.events).map((x) => { - if (x) return x(); - else return Promise.resolve(); - }), - ); - await Promise.all(Object.values(this.member_events).map((x) => x())); + // wait for event consumer cancellation + await Promise.all( + Object.values(this.events).map((x) => { + if (x) return x(); + else return Promise.resolve(); + }), + ); + await Promise.all(Object.values(this.member_events).map((x) => x())); - if (opts.channel) { - await opts.channel.close(); - opts.channel.off("error", handleChannelError); - } - }); + if (opts.channel) { + await opts.channel.close(); + opts.channel.off("error", handleChannelError); + } + }); } // TODO: only subscribe for events that are in the connection intents async function consume(this: WebSocket, opts: EventOpts) { - const { data, event } = opts; - const id = data.id as string; - const permission = this.permissions[id] || new Permissions("ADMINISTRATOR"); // default permission for dm + const { data, event } = opts; + const id = data.id as string; + const permission = this.permissions[id] || new Permissions("ADMINISTRATOR"); // default permission for dm - const consumer = consume.bind(this); - const listenOpts = opts as ListenEventOpts; - opts.acknowledge?.(); - // console.log("event", event); + const consumer = consume.bind(this); + const listenOpts = opts as ListenEventOpts; + opts.acknowledge?.(); + // console.log("event", event); - // subscription managment - switch (event) { - case "GUILD_MEMBER_REMOVE": - this.member_events[data.user.id]?.(); - delete this.member_events[data.user.id]; - break; - case "GUILD_MEMBER_ADD": - if (this.member_events[data.user.id]) break; // already subscribed - this.member_events[data.user.id] = await listenEvent(data.user.id, handlePresenceUpdate.bind(this), this.listen_options); - break; - case "GUILD_MEMBER_UPDATE": - if (!this.member_events[data.user.id]) break; - this.member_events[data.user.id](); - break; - case "RELATIONSHIP_REMOVE": - case "CHANNEL_DELETE": - case "GUILD_DELETE": - this.events[id]?.(); - delete this.events[id]; - if (event === "GUILD_DELETE" && this.ipAddress) { - const ban = await Ban.findOne({ - where: { guild_id: id, user_id: this.user_id }, - }); + // subscription managment + switch (event) { + case "GUILD_MEMBER_REMOVE": + this.member_events[data.user.id]?.(); + delete this.member_events[data.user.id]; + break; + case "GUILD_MEMBER_ADD": + if (this.member_events[data.user.id]) break; // already subscribed + this.member_events[data.user.id] = await listenEvent(data.user.id, handlePresenceUpdate.bind(this), this.listen_options); + break; + case "GUILD_MEMBER_UPDATE": + if (!this.member_events[data.user.id]) break; + this.member_events[data.user.id](); + break; + case "RELATIONSHIP_REMOVE": + case "CHANNEL_DELETE": + case "GUILD_DELETE": + this.events[id]?.(); + delete this.events[id]; + if (event === "GUILD_DELETE" && this.ipAddress) { + const ban = await Ban.findOne({ + where: { guild_id: id, user_id: this.user_id }, + }); - if (ban) { - ban.ip = this.ipAddress || undefined; - await ban.save(); - } - } - break; - case "CHANNEL_CREATE": - if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) return; - this.events[id] = await listenEvent(id, consumer, listenOpts); - break; - case "RELATIONSHIP_ADD": - this.events[data.user.id] = await listenEvent(data.user.id, handlePresenceUpdate.bind(this), this.listen_options); - break; - case "GUILD_CREATE": - this.events[id] = await listenEvent(id, consumer, listenOpts); - break; - case "CHANNEL_UPDATE": { - const exists = this.events[id]; - if (permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) { - if (exists) break; - this.events[id] = await listenEvent(id, consumer, listenOpts); - } else { - if (!exists) return; // return -> do not send channel update events for hidden channels - opts.cancel(id); - delete this.events[id]; - } - break; - } - } + if (ban) { + ban.ip = this.ipAddress || undefined; + await ban.save(); + } + } + break; + case "CHANNEL_CREATE": + if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) return; + this.events[id] = await listenEvent(id, consumer, listenOpts); + break; + case "RELATIONSHIP_ADD": + this.events[data.user.id] = await listenEvent(data.user.id, handlePresenceUpdate.bind(this), this.listen_options); + break; + case "GUILD_CREATE": + this.events[id] = await listenEvent(id, consumer, listenOpts); + break; + case "CHANNEL_UPDATE": { + const exists = this.events[id]; + if (permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) { + if (exists) break; + this.events[id] = await listenEvent(id, consumer, listenOpts); + } else { + if (!exists) return; // return -> do not send channel update events for hidden channels + opts.cancel(id); + delete this.events[id]; + } + break; + } + } - // permission checking - switch (event) { - case "INVITE_CREATE": - case "INVITE_DELETE": - case "GUILD_INTEGRATIONS_UPDATE": - if (!permission.has("MANAGE_GUILD")) return; - break; - case "WEBHOOKS_UPDATE": - if (!permission.has("MANAGE_WEBHOOKS")) return; - break; - case "GUILD_MEMBER_ADD": - case "GUILD_MEMBER_REMOVE": - case "GUILD_MEMBER_UPDATE": // only send them, if the user subscribed for this part of the member list, or is a bot - case "PRESENCE_UPDATE": // exception if user is friend - break; - case "GUILD_BAN_ADD": - case "GUILD_BAN_REMOVE": - if (!permission.has("BAN_MEMBERS")) return; - break; - case "VOICE_STATE_UPDATE": - case "MESSAGE_CREATE": - case "MESSAGE_DELETE": - case "MESSAGE_DELETE_BULK": - case "MESSAGE_UPDATE": - case "CHANNEL_PINS_UPDATE": - case "MESSAGE_REACTION_ADD": - case "MESSAGE_REACTION_REMOVE": - case "MESSAGE_REACTION_REMOVE_ALL": - case "MESSAGE_REACTION_REMOVE_EMOJI": - case "TYPING_START": - // only gets send if the user is alowed to view the current channel - if (!permission.has("VIEW_CHANNEL")) return; - break; - case "GUILD_CREATE": - case "GUILD_DELETE": - case "GUILD_UPDATE": - case "GUILD_ROLE_CREATE": - case "GUILD_ROLE_UPDATE": - case "GUILD_ROLE_DELETE": - case "CHANNEL_CREATE": - case "CHANNEL_DELETE": - case "CHANNEL_UPDATE": - case "GUILD_EMOJIS_UPDATE": - case "READY": // will be sent by the gateway - case "USER_UPDATE": - case "APPLICATION_COMMAND_CREATE": - case "APPLICATION_COMMAND_DELETE": - case "APPLICATION_COMMAND_UPDATE": - default: - // always gets sent - // Any events not defined in an intent are considered "passthrough" and will always be sent - break; - } + // permission checking + switch (event) { + case "INVITE_CREATE": + case "INVITE_DELETE": + case "GUILD_INTEGRATIONS_UPDATE": + if (!permission.has("MANAGE_GUILD")) return; + break; + case "WEBHOOKS_UPDATE": + if (!permission.has("MANAGE_WEBHOOKS")) return; + break; + case "GUILD_MEMBER_ADD": + case "GUILD_MEMBER_REMOVE": + case "GUILD_MEMBER_UPDATE": // only send them, if the user subscribed for this part of the member list, or is a bot + case "PRESENCE_UPDATE": // exception if user is friend + break; + case "GUILD_BAN_ADD": + case "GUILD_BAN_REMOVE": + if (!permission.has("BAN_MEMBERS")) return; + break; + case "VOICE_STATE_UPDATE": + case "MESSAGE_CREATE": + case "MESSAGE_DELETE": + case "MESSAGE_DELETE_BULK": + case "MESSAGE_UPDATE": + case "CHANNEL_PINS_UPDATE": + case "MESSAGE_REACTION_ADD": + case "MESSAGE_REACTION_REMOVE": + case "MESSAGE_REACTION_REMOVE_ALL": + case "MESSAGE_REACTION_REMOVE_EMOJI": + case "TYPING_START": + // only gets send if the user is alowed to view the current channel + if (!permission.has("VIEW_CHANNEL")) return; + break; + case "GUILD_CREATE": + case "GUILD_DELETE": + case "GUILD_UPDATE": + case "GUILD_ROLE_CREATE": + case "GUILD_ROLE_UPDATE": + case "GUILD_ROLE_DELETE": + case "CHANNEL_CREATE": + case "CHANNEL_DELETE": + case "CHANNEL_UPDATE": + case "GUILD_EMOJIS_UPDATE": + case "READY": // will be sent by the gateway + case "USER_UPDATE": + case "APPLICATION_COMMAND_CREATE": + case "APPLICATION_COMMAND_DELETE": + case "APPLICATION_COMMAND_UPDATE": + default: + // always gets sent + // Any events not defined in an intent are considered "passthrough" and will always be sent + break; + } - // data rewrites, e.g. signed attachment URLs - switch (event) { - case "MESSAGE_CREATE": - case "MESSAGE_UPDATE": - // console.log(this.request) - if (data["attachments"]) - data["attachments"] = Message.prototype.withSignedAttachments.call( - data, - new NewUrlUserSignatureData({ - ip: this.ipAddress, - userAgent: this.userAgent, - }), - ).attachments; - break; - default: - break; - } + // data rewrites, e.g. signed attachment URLs + switch (event) { + case "MESSAGE_CREATE": + case "MESSAGE_UPDATE": + // console.log(this.request) + if (data["attachments"]) + data["attachments"] = Message.prototype.withSignedAttachments.call( + data, + new NewUrlUserSignatureData({ + ip: this.ipAddress, + userAgent: this.userAgent, + }), + ).attachments; + break; + default: + break; + } - if (event === "GUILD_MEMBER_ADD") { - if ((data as PublicMember).roles === undefined || (data as PublicMember).roles === null) { - console.log(bgRedBright("[Gateway]"), "[GUILD_MEMBER_ADD] roles is undefined, setting to empty array!", opts.origin ?? "(Event origin not defined)", data); - (data as PublicMember).roles = []; - } - } + if (event === "GUILD_MEMBER_ADD") { + if ((data as PublicMember).roles === undefined || (data as PublicMember).roles === null) { + console.log(bgRedBright("[Gateway]"), "[GUILD_MEMBER_ADD] roles is undefined, setting to empty array!", opts.origin ?? "(Event origin not defined)", data); + (data as PublicMember).roles = []; + } + } - await Send(this, { - op: OPCODES.Dispatch, - t: event, - d: data, - s: this.sequence++, - }); + await Send(this, { + op: OPCODES.Dispatch, + t: event, + d: data, + s: this.sequence++, + }); } diff --git a/src/gateway/opcodes/GuildSubscriptionsBulk.ts b/src/gateway/opcodes/GuildSubscriptionsBulk.ts index 3c1bdbd..be69357 100644 --- a/src/gateway/opcodes/GuildSubscriptionsBulk.ts +++ b/src/gateway/opcodes/GuildSubscriptionsBulk.ts @@ -4,20 +4,20 @@ import { check } from "./instanceOf"; export async function onGuildSubscriptionsBulk(this: WebSocket, payload: Payload) { - const startTime = Date.now(); - check.call(this, GuildSubscriptionsBulkSchema, payload.d); - const body = payload.d as GuildSubscriptionsBulkSchema; + const startTime = Date.now(); + check.call(this, GuildSubscriptionsBulkSchema, payload.d); + const body = payload.d as GuildSubscriptionsBulkSchema; - let guildId: keyof GuildSubscriptionsBulkSchema["subscriptions"]; + let guildId: keyof GuildSubscriptionsBulkSchema["subscriptions"]; - for (guildId in body.subscriptions) { - await onLazyRequest.call(this, { - ...payload, - d: { - guild_id: guildId, - ...body.subscriptions[guildId], - }, - }); - } - console.log(`[Gateway] GuildSubscriptionsBulk processed ${Object.keys(body.subscriptions).length} subscriptions for user ${this.user_id} in ${Date.now() - startTime}ms`); + for (guildId in body.subscriptions) { + await onLazyRequest.call(this, { + ...payload, + d: { + guild_id: guildId, + ...body.subscriptions[guildId], + }, + }); + } + console.log(`[Gateway] GuildSubscriptionsBulk processed ${Object.keys(body.subscriptions).length} subscriptions for user ${this.user_id} in ${Date.now() - startTime}ms`); } diff --git a/src/gateway/opcodes/GuildSync.ts b/src/gateway/opcodes/GuildSync.ts index 03540de..6eea199 100644 --- a/src/gateway/opcodes/GuildSync.ts +++ b/src/gateway/opcodes/GuildSync.ts @@ -17,20 +17,20 @@ */ import { - getDatabase, - getPermission, - listenEvent, - Member, - Role, - Session, - User, - Presence, - Channel, - Permissions, - arrayPartition, - timePromise, - Stopwatch, - Guild, + getDatabase, + getPermission, + listenEvent, + Member, + Role, + Session, + User, + Presence, + Channel, + Permissions, + arrayPartition, + timePromise, + Stopwatch, + Guild, } from "@spacebar/util"; import { WebSocket, Payload, handlePresenceUpdate, OPCODES, Send } from "@spacebar/gateway"; import murmur from "murmurhash-js/murmurhash3_gc"; @@ -43,85 +43,85 @@ // TODO: rewrite typeorm const getMostRelevantSession = (sessions: Session[]) => { - const statusMap = { - online: 0, - idle: 1, - dnd: 2, - invisible: 3, - offline: 4, - }; - // sort sessions by relevance - sessions = sessions.sort((a, b) => { - return statusMap[a.status] - statusMap[b.status] + ((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2; - }); + const statusMap = { + online: 0, + idle: 1, + dnd: 2, + invisible: 3, + offline: 4, + }; + // sort sessions by relevance + sessions = sessions.sort((a, b) => { + return statusMap[a.status] - statusMap[b.status] + ((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2; + }); - return sessions[0]; + return sessions[0]; }; export async function onGuildSync(this: WebSocket, { d }: Payload) { - const sw = Stopwatch.startNew(); - if (!Array.isArray(d)) throw new Error("Invalid payload for GUILD_SYNC"); - const guild_ids = d as string[]; + const sw = Stopwatch.startNew(); + if (!Array.isArray(d)) throw new Error("Invalid payload for GUILD_SYNC"); + const guild_ids = d as string[]; - const joinedGuildIds = await Member.find({ where: { id: this.user_id, guild_id: In(guild_ids) }, select: { guild_id: true } }).then((members) => - members.map((m) => m.guild_id), - ); + const joinedGuildIds = await Member.find({ where: { id: this.user_id, guild_id: In(guild_ids) }, select: { guild_id: true } }).then((members) => + members.map((m) => m.guild_id), + ); - const tasks = joinedGuildIds.map((guildId) => timePromise(async () => handleGuildSync(this, guildId))); - // not awaiting lol - Promise.all(tasks) - .then((res) => { - console.log(`[Gateway] GUILD_SYNC processed ${guild_ids.length} guilds in ${sw.elapsed().totalMilliseconds}ms:`, { - ...Object.fromEntries( - res.map((r) => [r.result.id, `${r.result.id}: ${r.result.members.length}U/${r.result.presences.length}P in ${r.elapsed.totalMilliseconds}ms`]), - ), - }); - }) - .catch((err) => { - console.error("[Gateway] Error processing GUILD_SYNC:", err); - }); + const tasks = joinedGuildIds.map((guildId) => timePromise(async () => handleGuildSync(this, guildId))); + // not awaiting lol + Promise.all(tasks) + .then((res) => { + console.log(`[Gateway] GUILD_SYNC processed ${guild_ids.length} guilds in ${sw.elapsed().totalMilliseconds}ms:`, { + ...Object.fromEntries( + res.map((r) => [r.result.id, `${r.result.id}: ${r.result.members.length}U/${r.result.presences.length}P in ${r.elapsed.totalMilliseconds}ms`]), + ), + }); + }) + .catch((err) => { + console.error("[Gateway] Error processing GUILD_SYNC:", err); + }); } interface GuildSyncResult { - id: string; - presences: Presence[]; - members: PublicMember[]; + id: string; + presences: Presence[]; + members: PublicMember[]; } async function handleGuildSync(ws: WebSocket, guild_id: string) { - const res: GuildSyncResult = { id: guild_id, presences: [], members: [] }; + const res: GuildSyncResult = { id: guild_id, presences: [], members: [] }; - const members = await Member.find({ where: { guild_id }, relations: ["user", "roles", "guild"] }); - res.members = members.map((m) => m.toPublicMember()); + const members = await Member.find({ where: { guild_id }, relations: ["user", "roles", "guild"] }); + res.members = members.map((m) => m.toPublicMember()); - const sessions = await Session.find({ where: { user_id: In(members.map((m) => m.id)) }, order: { user_id: "ASC" } }); - const sessionsByUserId = new Map(); - for (const session of sessions) { - if (!sessionsByUserId.has(session.user_id)) sessionsByUserId.set(session.user_id, []); - sessionsByUserId.get(session.user_id)!.push(session); - } + const sessions = await Session.find({ where: { user_id: In(members.map((m) => m.id)) }, order: { user_id: "ASC" } }); + const sessionsByUserId = new Map(); + for (const session of sessions) { + if (!sessionsByUserId.has(session.user_id)) sessionsByUserId.set(session.user_id, []); + sessionsByUserId.get(session.user_id)!.push(session); + } - for (const member of members) { - const userSessions = sessionsByUserId.get(member.id) || []; - if (userSessions.length === 0) continue; + for (const member of members) { + const userSessions = sessionsByUserId.get(member.id) || []; + if (userSessions.length === 0) continue; - const mostRelevantSession = getMostRelevantSession(userSessions); - const presence: Presence = { - user: member.user.toPublicUser(), - guild_id: guild_id, - status: mostRelevantSession.getPublicStatus(), - activities: mostRelevantSession.activities, - client_status: mostRelevantSession.client_status, - }; - res.presences.push(presence); - } + const mostRelevantSession = getMostRelevantSession(userSessions); + const presence: Presence = { + user: member.user.toPublicUser(), + guild_id: guild_id, + status: mostRelevantSession.getPublicStatus(), + activities: mostRelevantSession.activities, + client_status: mostRelevantSession.client_status, + }; + res.presences.push(presence); + } - await Send(ws, { - op: OPCODES.Dispatch, - t: "GUILD_SYNC", - s: ws.sequence++, - d: res, - }); + await Send(ws, { + op: OPCODES.Dispatch, + t: "GUILD_SYNC", + s: ws.sequence++, + d: res, + }); - return res; + return res; } diff --git a/src/gateway/opcodes/Heartbeat.ts b/src/gateway/opcodes/Heartbeat.ts index f494866..5e60a65 100644 --- a/src/gateway/opcodes/Heartbeat.ts +++ b/src/gateway/opcodes/Heartbeat.ts @@ -23,37 +23,37 @@ import { FindOptionsWhere } from "typeorm"; interface QoSData { - seq: number | null; - qos: QoSPayload; + seq: number | null; + qos: QoSPayload; } export interface QoSPayload { - ver: number; - active: boolean; - reasons: string[]; + ver: number; + active: boolean; + reasons: string[]; } export async function onHeartbeat(this: WebSocket, data: Payload) { - // TODO: validate payload + // TODO: validate payload - setHeartbeat(this); + setHeartbeat(this); - if (data.op === OPCODES.SetQoS) { - this.qos = (data.d as QoSData).qos; - } + if (data.op === OPCODES.SetQoS) { + this.qos = (data.d as QoSData).qos; + } - const newSessionData: Partial = { - last_seen: new Date(), - }; + const newSessionData: Partial = { + last_seen: new Date(), + }; - await Promise.all([ - Send(this, { op: 11, d: {} }), - Session.update( - { - session_id: this.session_id!, - user_id: this.user_id, - } as FindOptionsWhere, - newSessionData, - ), - ]); + await Promise.all([ + Send(this, { op: 11, d: {} }), + Session.update( + { + session_id: this.session_id!, + user_id: this.user_id, + } as FindOptionsWhere, + newSessionData, + ), + ]); } diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 30a49c1..d85c243 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -18,42 +18,42 @@ import { CLOSECODES, Capabilities, OPCODES, Payload, Send, WebSocket, setupListener } from "@spacebar/gateway"; import { - Application, - Config, - EVENTEnum, - Guild, - GuildOrUnavailable, - Intents, - Member, - MemberPrivateProjection, - OPCodes, - PresenceUpdateEvent, - PrivateSessionProjection, - ReadState, - ReadyEventData, - ReadyGuildDTO, - ReadyUserGuildSettingsEntries, - Recipient, - Session, - SessionsReplace, - UserSettings, - checkToken, - emitEvent, - getDatabase, - TraceNode, - TraceRoot, - Stopwatch, - timePromise, - ElapsedTime, - Channel, - Emoji, - Role, - Sticker, - VoiceState, - UserSettingsProtos, - IpDataClient, - generateToken, - CurrentTokenFormatVersion, + Application, + Config, + EVENTEnum, + Guild, + GuildOrUnavailable, + Intents, + Member, + MemberPrivateProjection, + OPCodes, + PresenceUpdateEvent, + PrivateSessionProjection, + ReadState, + ReadyEventData, + ReadyGuildDTO, + ReadyUserGuildSettingsEntries, + Recipient, + Session, + SessionsReplace, + UserSettings, + checkToken, + emitEvent, + getDatabase, + TraceNode, + TraceRoot, + Stopwatch, + timePromise, + ElapsedTime, + Channel, + Emoji, + Role, + Sticker, + VoiceState, + UserSettingsProtos, + IpDataClient, + generateToken, + CurrentTokenFormatVersion, } from "@spacebar/util"; import { check } from "./instanceOf"; import { In } from "typeorm"; @@ -64,344 +64,344 @@ // TODO: check privileged intents, if defined in the config function logAuth(message: string) { - if (process.env.LOG_AUTH != "true") return; - console.log(`[Gateway/Auth] ${message}`); + if (process.env.LOG_AUTH != "true") return; + console.log(`[Gateway/Auth] ${message}`); } const tryGetUserFromToken = async (...args: Parameters) => { - logAuth("Checking token"); - try { - return (await checkToken(...args)).user; - } catch (e) { - console.log("[Gateway] Error when identifying: ", e); - return null; - } + logAuth("Checking token"); + try { + return (await checkToken(...args)).user; + } catch (e) { + console.log("[Gateway] Error when identifying: ", e); + return null; + } }; export async function onIdentify(this: WebSocket, data: Payload) { - const totalSw = Stopwatch.startNew(); - const taskSw = Stopwatch.startNew(); - const gatewayShardName = `sb-gateway`; + const totalSw = Stopwatch.startNew(); + const taskSw = Stopwatch.startNew(); + const gatewayShardName = `sb-gateway`; - if (this.user_id) { - // we've already identified - return this.close(CLOSECODES.Already_authenticated); - } + if (this.user_id) { + // we've already identified + return this.close(CLOSECODES.Already_authenticated); + } - clearTimeout(this.readyTimeout); + clearTimeout(this.readyTimeout); - // Check payload matches schema - check.call(this, IdentifySchema, data.d); - const identify: IdentifySchema = data.d; + // Check payload matches schema + check.call(this, IdentifySchema, data.d); + const identify: IdentifySchema = data.d; - this.capabilities = new Capabilities(identify.capabilities || 0); - this.large_threshold = identify.large_threshold || 250; - const parseAndValidateTime = taskSw.getElapsedAndReset(); + this.capabilities = new Capabilities(identify.capabilities || 0); + this.large_threshold = identify.large_threshold || 250; + const parseAndValidateTime = taskSw.getElapsedAndReset(); - const tokenData = await checkToken(identify.token, { - relations: ["relationships", "relationships.to", "settings"], - select: [...PrivateUserProjection, "relationships", "rights"], - }); + const tokenData = await checkToken(identify.token, { + relations: ["relationships", "relationships.to", "settings"], + select: [...PrivateUserProjection, "relationships", "rights"], + }); - const user = tokenData.user; - if (!user) { - console.log("[Gateway] Failed to identify user"); - return this.close(CLOSECODES.Authentication_failed); - } - this.user_id = user.id; - this.session = tokenData.session; - const userQueryTime = taskSw.getElapsedAndReset(); + const user = tokenData.user; + if (!user) { + console.log("[Gateway] Failed to identify user"); + return this.close(CLOSECODES.Authentication_failed); + } + this.user_id = user.id; + this.session = tokenData.session; + const userQueryTime = taskSw.getElapsedAndReset(); - // Check intents - if (!identify.intents) identify.intents = 0b11011111111111111111111111111111111n; // TODO: what is this number? - this.intents = new Intents(identify.intents); + // Check intents + if (!identify.intents) identify.intents = 0b11011111111111111111111111111111111n; // TODO: what is this number? + this.intents = new Intents(identify.intents); - // TODO: actually do intent things. + // TODO: actually do intent things. - // Validate sharding - if (identify.shard) { - this.shard_id = identify.shard[0]; - this.shard_count = identify.shard[1]; + // Validate sharding + if (identify.shard) { + this.shard_id = identify.shard[0]; + this.shard_count = identify.shard[1]; - if (this.shard_count == null || this.shard_id == null || this.shard_id > this.shard_count || this.shard_id < 0 || this.shard_count <= 0) { - // TODO: why do we even care about this right now? - console.log(`[Gateway] Invalid sharding from ${user.id}: ${identify.shard}`); - return this.close(CLOSECODES.Invalid_shard); - } - } - const validateIntentsAndShardingTime = taskSw.getElapsedAndReset(); + if (this.shard_count == null || this.shard_id == null || this.shard_id > this.shard_count || this.shard_id < 0 || this.shard_count <= 0) { + // TODO: why do we even care about this right now? + console.log(`[Gateway] Invalid sharding from ${user.id}: ${identify.shard}`); + return this.close(CLOSECODES.Invalid_shard); + } + } + const validateIntentsAndShardingTime = taskSw.getElapsedAndReset(); - // Generate a new gateway session ( id is already made, just save it in db ) - this.session = - tokenData.session ?? - Session.create({ - user_id: this.user_id, - session_id: this.session_id, - }); + // Generate a new gateway session ( id is already made, just save it in db ) + this.session = + tokenData.session ?? + Session.create({ + user_id: this.user_id, + session_id: this.session_id, + }); - this.session.status = identify.presence?.status || "online"; - this.session.last_seen = new Date(); - this.session.client_info ??= {}; - this.session.client_info.platform = identify.properties?.$device ?? identify.properties?.$device; - this.session.client_info.os = identify.properties?.os || identify.properties?.$os; - this.session.client_status = {}; - this.session.activities = identify.presence?.activities ?? []; // TODO: validation + this.session.status = identify.presence?.status || "online"; + this.session.last_seen = new Date(); + this.session.client_info ??= {}; + this.session.client_info.platform = identify.properties?.$device ?? identify.properties?.$device; + this.session.client_info.os = identify.properties?.os || identify.properties?.$os; + this.session.client_status = {}; + this.session.activities = identify.presence?.activities ?? []; // TODO: validation - if (this.ipAddress && this.ipAddress !== this.session.last_seen_ip) { - this.session.last_seen_ip = this.ipAddress; - await this.session.updateIpInfo(); - } + if (this.ipAddress && this.ipAddress !== this.session.last_seen_ip) { + this.session.last_seen_ip = this.ipAddress; + await this.session.updateIpInfo(); + } - const createSessionTime = taskSw.getElapsedAndReset(); + const createSessionTime = taskSw.getElapsedAndReset(); - // Get from database: - // * the users read states - // * guild members for this user - // * recipients ( dm channels ) - // * the bot application, if it exists - const [ - { elapsed: sessionSaveTime }, - { result: application, elapsed: applicationQueryTime }, - { result: read_states, elapsed: read_statesQueryTime }, - { result: members, elapsed: membersQueryTime }, - { result: recipients, elapsed: recipientsQueryTime }, - ] = await Promise.all([ - timePromise(() => this.session!.save()), + // Get from database: + // * the users read states + // * guild members for this user + // * recipients ( dm channels ) + // * the bot application, if it exists + const [ + { elapsed: sessionSaveTime }, + { result: application, elapsed: applicationQueryTime }, + { result: read_states, elapsed: read_statesQueryTime }, + { result: members, elapsed: membersQueryTime }, + { result: recipients, elapsed: recipientsQueryTime }, + ] = await Promise.all([ + timePromise(() => this.session!.save()), - timePromise(() => - Application.findOne({ - where: { id: this.user_id }, - select: ["id", "flags"], - }), - ), + timePromise(() => + Application.findOne({ + where: { id: this.user_id }, + select: ["id", "flags"], + }), + ), - timePromise(() => - ReadState.find({ - where: { user_id: this.user_id }, - select: ["id", "channel_id", "last_message_id", "last_pin_timestamp", "mention_count"], - }), - ), + timePromise(() => + ReadState.find({ + where: { user_id: this.user_id }, + select: ["id", "channel_id", "last_message_id", "last_pin_timestamp", "mention_count"], + }), + ), - timePromise(() => - Member.find({ - where: { id: this.user_id }, - select: { - // We only want some member props - ...Object.fromEntries(MemberPrivateProjection.map((x) => [x, true])), - settings: true, // guild settings - roles: { id: true }, // the full role is fetched from the `guild` relation - guild: { id: true }, + timePromise(() => + Member.find({ + where: { id: this.user_id }, + select: { + // We only want some member props + ...Object.fromEntries(MemberPrivateProjection.map((x) => [x, true])), + settings: true, // guild settings + roles: { id: true }, // the full role is fetched from the `guild` relation + guild: { id: true }, - // TODO: we don't really need every property of - // guild channels, emoji, roles, stickers - // but we do want almost everything from guild. - // How do you do that without just enumerating the guild props? - // guild: Object.fromEntries( - // getDatabase()! - // .getMetadata(Guild) - // .columns.map((x) => [x.propertyName, true]), - // ), - }, - relations: [ - // "guild", - // "guild.channels", - // "guild.emojis", - // "guild.roles", - // "guild.stickers", - // "guild.voice_states", - "roles", + // TODO: we don't really need every property of + // guild channels, emoji, roles, stickers + // but we do want almost everything from guild. + // How do you do that without just enumerating the guild props? + // guild: Object.fromEntries( + // getDatabase()! + // .getMetadata(Guild) + // .columns.map((x) => [x.propertyName, true]), + // ), + }, + relations: [ + // "guild", + // "guild.channels", + // "guild.emojis", + // "guild.roles", + // "guild.stickers", + // "guild.voice_states", + "roles", - // For these entities, `user` is always just the logged in user we fetched above - // "user", - ], - }), - ), + // For these entities, `user` is always just the logged in user we fetched above + // "user", + ], + }), + ), - timePromise(() => - Recipient.find({ - where: { user_id: this.user_id, closed: false }, - relations: ["channel", "channel.recipients", "channel.recipients.user"], - select: { - channel: { - id: true, - flags: true, - // is_spam: true, // TODO - last_message_id: true, - last_pin_timestamp: true, - type: true, - icon: true, - name: true, - owner_id: true, - recipients: { - // we don't actually need this ID or any other information about the recipient info, - // but typeorm does not select anything from the users relation of recipients unless we select - // at least one column. - id: true, - // We only want public user data for each dm channel - user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), - }, - }, - }, - }), - ), - ]); + timePromise(() => + Recipient.find({ + where: { user_id: this.user_id, closed: false }, + relations: ["channel", "channel.recipients", "channel.recipients.user"], + select: { + channel: { + id: true, + flags: true, + // is_spam: true, // TODO + last_message_id: true, + last_pin_timestamp: true, + type: true, + icon: true, + name: true, + owner_id: true, + recipients: { + // we don't actually need this ID or any other information about the recipient info, + // but typeorm does not select anything from the users relation of recipients unless we select + // at least one column. + id: true, + // We only want public user data for each dm channel + user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), + }, + }, + }, + }), + ), + ]); - const { result: memberGuilds, elapsed: queryGuildsTime } = await timePromise(() => - Promise.all( - members.map((m) => - Guild.findOneOrFail({ - where: { id: m.guild_id }, - select: Object.fromEntries( - getDatabase()! - .getMetadata(Guild) - .columns.map((x) => [x.propertyName, true]), - ), - // relations: ["channels", "emojis", "roles", "stickers", "voice_states"], - }), - ), - ), - ); + const { result: memberGuilds, elapsed: queryGuildsTime } = await timePromise(() => + Promise.all( + members.map((m) => + Guild.findOneOrFail({ + where: { id: m.guild_id }, + select: Object.fromEntries( + getDatabase()! + .getMetadata(Guild) + .columns.map((x) => [x.propertyName, true]), + ), + // relations: ["channels", "emojis", "roles", "stickers", "voice_states"], + }), + ), + ), + ); - const guildIds = memberGuilds.map((g) => g.id); + const guildIds = memberGuilds.map((g) => g.id); - // select relations - const [ - { result: settingsProtos, elapsed: settingsProtosQueryTime }, - { result: memberGuildChannels, elapsed: queryGuildChannelsTime }, - { result: memberGuildEmojis, elapsed: queryGuildEmojisTime }, - { result: memberGuildRoles, elapsed: queryGuildRolesTime }, - { result: memberGuildStickers, elapsed: queryGuildStickersTime }, - { result: memberGuildVoiceStates, elapsed: queryGuildVoiceStatesTime }, - ] = await Promise.all([ - timePromise(() => - UserSettingsProtos.findOne({ - where: { user_id: this.user_id }, - }), - ), - timePromise(() => - Channel.find({ - where: { guild_id: In(guildIds) }, - order: { guild_id: "ASC" }, - }), - ), - timePromise(() => - Emoji.find({ - where: { guild_id: In(guildIds) }, - order: { guild_id: "ASC" }, - }), - ), - timePromise(() => - Role.find({ - where: { guild_id: In(guildIds) }, - order: { guild_id: "ASC" }, - }), - ), - timePromise(() => - Sticker.find({ - where: { guild_id: In(guildIds) }, - order: { guild_id: "ASC" }, - }), - ), - timePromise(() => - VoiceState.find({ - where: { guild_id: In(guildIds) }, - order: { guild_id: "ASC" }, - }), - ), - ]); + // select relations + const [ + { result: settingsProtos, elapsed: settingsProtosQueryTime }, + { result: memberGuildChannels, elapsed: queryGuildChannelsTime }, + { result: memberGuildEmojis, elapsed: queryGuildEmojisTime }, + { result: memberGuildRoles, elapsed: queryGuildRolesTime }, + { result: memberGuildStickers, elapsed: queryGuildStickersTime }, + { result: memberGuildVoiceStates, elapsed: queryGuildVoiceStatesTime }, + ] = await Promise.all([ + timePromise(() => + UserSettingsProtos.findOne({ + where: { user_id: this.user_id }, + }), + ), + timePromise(() => + Channel.find({ + where: { guild_id: In(guildIds) }, + order: { guild_id: "ASC" }, + }), + ), + timePromise(() => + Emoji.find({ + where: { guild_id: In(guildIds) }, + order: { guild_id: "ASC" }, + }), + ), + timePromise(() => + Role.find({ + where: { guild_id: In(guildIds) }, + order: { guild_id: "ASC" }, + }), + ), + timePromise(() => + Sticker.find({ + where: { guild_id: In(guildIds) }, + order: { guild_id: "ASC" }, + }), + ), + timePromise(() => + VoiceState.find({ + where: { guild_id: In(guildIds) }, + order: { guild_id: "ASC" }, + }), + ), + ]); - const mergeMemberGuildsTrace: TraceNode = { - micros: 0, - calls: [], - }; - members.forEach((m) => { - const sw = Stopwatch.startNew(); - const totalSw = Stopwatch.startNew(); - const trace: TraceNode = { - micros: 0, - calls: [], - }; + const mergeMemberGuildsTrace: TraceNode = { + micros: 0, + calls: [], + }; + members.forEach((m) => { + const sw = Stopwatch.startNew(); + const totalSw = Stopwatch.startNew(); + const trace: TraceNode = { + micros: 0, + calls: [], + }; - const g = memberGuilds.find((mg) => mg.id === m.guild_id); - if (g) { - m.guild = g; - trace.calls.push("findGuild", { micros: sw.getElapsedAndReset().totalMicroseconds }); + const g = memberGuilds.find((mg) => mg.id === m.guild_id); + if (g) { + m.guild = g; + trace.calls.push("findGuild", { micros: sw.getElapsedAndReset().totalMicroseconds }); - //channels - g.channels = memberGuildChannels.filter((c) => c.guild_id === m.guild_id); - trace.calls.push("filterChannels", { micros: sw.getElapsedAndReset().totalMicroseconds }); + //channels + g.channels = memberGuildChannels.filter((c) => c.guild_id === m.guild_id); + trace.calls.push("filterChannels", { micros: sw.getElapsedAndReset().totalMicroseconds }); - //emojis - g.emojis = memberGuildEmojis.filter((e) => e.guild_id === m.guild_id); - trace.calls.push("filterEmojis", { micros: sw.getElapsedAndReset().totalMicroseconds }); + //emojis + g.emojis = memberGuildEmojis.filter((e) => e.guild_id === m.guild_id); + trace.calls.push("filterEmojis", { micros: sw.getElapsedAndReset().totalMicroseconds }); - //roles - g.roles = memberGuildRoles.filter((r) => r.guild_id === m.guild_id); - trace.calls.push("filterRoles", { micros: sw.getElapsedAndReset().totalMicroseconds }); + //roles + g.roles = memberGuildRoles.filter((r) => r.guild_id === m.guild_id); + trace.calls.push("filterRoles", { micros: sw.getElapsedAndReset().totalMicroseconds }); - //stickers - g.stickers = memberGuildStickers.filter((s) => s.guild_id === m.guild_id); - trace.calls.push("filterStickers", { micros: sw.getElapsedAndReset().totalMicroseconds }); + //stickers + g.stickers = memberGuildStickers.filter((s) => s.guild_id === m.guild_id); + trace.calls.push("filterStickers", { micros: sw.getElapsedAndReset().totalMicroseconds }); - //voice states - g.voice_states = memberGuildVoiceStates.filter((v) => v.guild_id === m.guild_id); - trace.calls.push("filterVoiceStates", { micros: sw.getElapsedAndReset().totalMicroseconds }); + //voice states + g.voice_states = memberGuildVoiceStates.filter((v) => v.guild_id === m.guild_id); + trace.calls.push("filterVoiceStates", { micros: sw.getElapsedAndReset().totalMicroseconds }); - //total - trace.micros = totalSw.elapsed().totalMicroseconds; - mergeMemberGuildsTrace.calls!.push(`guild_${m.guild_id}`, trace); - } else { - console.error(`[Gateway] Member ${m.id} has invalid guild_id ${m.guild_id}`); - mergeMemberGuildsTrace.calls!.push(`guild_~~${m.guild_id}~~`, trace); - } - }); + //total + trace.micros = totalSw.elapsed().totalMicroseconds; + mergeMemberGuildsTrace.calls!.push(`guild_${m.guild_id}`, trace); + } else { + console.error(`[Gateway] Member ${m.id} has invalid guild_id ${m.guild_id}`); + mergeMemberGuildsTrace.calls!.push(`guild_~~${m.guild_id}~~`, trace); + } + }); - for (const call of mergeMemberGuildsTrace.calls!) { - if (typeof call !== "string") mergeMemberGuildsTrace.micros += (call as { micros: number }).micros; - } + for (const call of mergeMemberGuildsTrace.calls!) { + if (typeof call !== "string") mergeMemberGuildsTrace.micros += (call as { micros: number }).micros; + } - const totalQueryTime = taskSw.getElapsedAndReset(); + const totalQueryTime = taskSw.getElapsedAndReset(); - // We forgot to migrate user settings from the JSON column of `users` - // to the `user_settings` table theyre in now, - // so for instances that migrated, users may not have a `user_settings` row. - let createUserSettingsTime: ElapsedTime | undefined = undefined; - if (!user.settings) { - user.settings = await UserSettings.getOrDefault(user.id); - createUserSettingsTime = taskSw.getElapsedAndReset(); - } + // We forgot to migrate user settings from the JSON column of `users` + // to the `user_settings` table theyre in now, + // so for instances that migrated, users may not have a `user_settings` row. + let createUserSettingsTime: ElapsedTime | undefined = undefined; + if (!user.settings) { + user.settings = await UserSettings.getOrDefault(user.id); + createUserSettingsTime = taskSw.getElapsedAndReset(); + } - // Generate merged_members - const merged_members = members.map((x) => { - return [ - { - ...x, - // filter out @everyone role - roles: x.roles.filter((r) => r.id !== x.guild.id).map((x) => x.id), + // Generate merged_members + const merged_members = members.map((x) => { + return [ + { + ...x, + // filter out @everyone role + roles: x.roles.filter((r) => r.id !== x.guild.id).map((x) => x.id), - // add back user, which we don't fetch from db - // TODO: For guild profiles, this may need to be changed. - // TODO: The only field required in the user prop is `id`, - // but our types are annoying so I didn't bother. - user: user.toPublicUser(), + // add back user, which we don't fetch from db + // TODO: For guild profiles, this may need to be changed. + // TODO: The only field required in the user prop is `id`, + // but our types are annoying so I didn't bother. + user: user.toPublicUser(), - guild: { - id: x.guild.id, - }, - settings: undefined, - }, - ]; - }); - const mergedMembersTime = taskSw.getElapsedAndReset(); + guild: { + id: x.guild.id, + }, + settings: undefined, + }, + ]; + }); + const mergedMembersTime = taskSw.getElapsedAndReset(); - // Populated with guilds 'unavailable' currently - // Just for bots - const pending_guilds: Guild[] = []; + // Populated with guilds 'unavailable' currently + // Just for bots + const pending_guilds: Guild[] = []; - // Generate guilds list ( make them unavailable if user is bot ) - const guilds: GuildOrUnavailable[] = members.map((member) => { - member.guild.channels = member.guild.channels - /* + // Generate guilds list ( make them unavailable if user is bot ) + const guilds: GuildOrUnavailable[] = members.map((member) => { + member.guild.channels = member.guild.channels + /* //TODO maybe implement this correctly, by causing create and delete events for users who can newly view and not view the channels, along with doing these checks correctly, as they don't currently take into account that the owner of the guild is always able to view channels, with potentially other issues .filter((channel) => { const perms = Permissions.finalPermission({ @@ -416,312 +416,312 @@ return perms.has("VIEW_CHANNEL"); }) */ - .map((channel) => { - channel.position = member.guild.channel_ordering.indexOf(channel.id); - return channel; - }) - .sort((a, b) => a.position - b.position); + .map((channel) => { + channel.position = member.guild.channel_ordering.indexOf(channel.id); + return channel; + }) + .sort((a, b) => a.position - b.position); - if (user.bot) { - pending_guilds.push(member.guild); - return { id: member.guild.id, unavailable: true }; - } + if (user.bot) { + pending_guilds.push(member.guild); + return { id: member.guild.id, unavailable: true }; + } - return { - ...member.guild.toJSON(), - joined_at: member.joined_at, + return { + ...member.guild.toJSON(), + joined_at: member.joined_at, - threads: [], - }; - }); - const generateGuildsListTime = taskSw.getElapsedAndReset(); + threads: [], + }; + }); + const generateGuildsListTime = taskSw.getElapsedAndReset(); - // Generate user_guild_settings - const user_guild_settings_entries: ReadyUserGuildSettingsEntries[] = members.map((x) => ({ - ...DefaultUserGuildSettings, - ...x.settings, - guild_id: x.guild_id, - channel_overrides: Object.entries(x.settings.channel_overrides ?? {}).map((y) => ({ - ...y[1], - channel_id: y[0], - })), - })); - const generateUserGuildSettingsTime = taskSw.getElapsedAndReset(); + // Generate user_guild_settings + const user_guild_settings_entries: ReadyUserGuildSettingsEntries[] = members.map((x) => ({ + ...DefaultUserGuildSettings, + ...x.settings, + guild_id: x.guild_id, + channel_overrides: Object.entries(x.settings.channel_overrides ?? {}).map((y) => ({ + ...y[1], + channel_id: y[0], + })), + })); + const generateUserGuildSettingsTime = taskSw.getElapsedAndReset(); - // Populated with users from private channels, relationships. - // Uses a set to dedupe for us. - const users: Set = new Set(); + // Populated with users from private channels, relationships. + // Uses a set to dedupe for us. + const users: Set = new Set(); - // Generate dm channels from recipients list. Append recipients to `users` list - const channels = recipients - .filter(({ channel }) => channel.isDm()) - .map((r) => { - // TODO: fix the types of Recipient - // Their channels are only ever private (I think) and thus are always DM channels - const channel = r.channel as DMChannel; + // Generate dm channels from recipients list. Append recipients to `users` list + const channels = recipients + .filter(({ channel }) => channel.isDm()) + .map((r) => { + // TODO: fix the types of Recipient + // Their channels are only ever private (I think) and thus are always DM channels + const channel = r.channel as DMChannel; - // Remove ourself from the list of other users in dm channel - channel.recipients = channel.recipients.filter((recipient) => recipient.user.id !== this.user_id); + // Remove ourself from the list of other users in dm channel + channel.recipients = channel.recipients.filter((recipient) => recipient.user.id !== this.user_id); - const channelUsers = channel.recipients?.map((recipient) => recipient.user.toPublicUser()); + const channelUsers = channel.recipients?.map((recipient) => recipient.user.toPublicUser()); - if (channelUsers && channelUsers.length > 0) channelUsers.forEach((user) => users.add(user)); - return { - id: channel.id, - flags: channel.flags, - last_message_id: channel.last_message_id, - type: channel.type, - recipients: channelUsers || [], - icon: channel.icon, - name: channel.name, - is_spam: false, // TODO - owner_id: channel.owner_id || undefined, - }; - }); - const generateDmChannelsTime = taskSw.getElapsedAndReset(); + if (channelUsers && channelUsers.length > 0) channelUsers.forEach((user) => users.add(user)); + return { + id: channel.id, + flags: channel.flags, + last_message_id: channel.last_message_id, + type: channel.type, + recipients: channelUsers || [], + icon: channel.icon, + name: channel.name, + is_spam: false, // TODO + owner_id: channel.owner_id || undefined, + }; + }); + const generateDmChannelsTime = taskSw.getElapsedAndReset(); - // From user relationships ( friends ), also append to `users` list - user.relationships.forEach((x) => users.add(x.to.toPublicUser())); - const appendRelationshipsTime = taskSw.getElapsedAndReset(); + // From user relationships ( friends ), also append to `users` list + user.relationships.forEach((x) => users.add(x.to.toPublicUser())); + const appendRelationshipsTime = taskSw.getElapsedAndReset(); - // Send SESSIONS_REPLACE and PRESENCE_UPDATE - const allSessions = ( - await Session.find({ - where: { user_id: this.user_id, is_admin_session: false }, - }) - ).map((x) => x.toPrivateGatewayDeviceInfo()); - // ).map((x) => ({ - // // TODO how is active determined? - // // in our lazy request impl, we just pick the 'most relevant' session - // active: x.session_id == this.session!.session_id, - // activities: x.activities ?? [], - // client_info: x.client_info, - // session_id: x.session_id, // TODO: discord.com sends 'all', what is that??? - // status: x.status, - // })); - const findAndGenerateSessionReplaceTime = taskSw.getElapsedAndReset(); + // Send SESSIONS_REPLACE and PRESENCE_UPDATE + const allSessions = ( + await Session.find({ + where: { user_id: this.user_id, is_admin_session: false }, + }) + ).map((x) => x.toPrivateGatewayDeviceInfo()); + // ).map((x) => ({ + // // TODO how is active determined? + // // in our lazy request impl, we just pick the 'most relevant' session + // active: x.session_id == this.session!.session_id, + // activities: x.activities ?? [], + // client_info: x.client_info, + // session_id: x.session_id, // TODO: discord.com sends 'all', what is that??? + // status: x.status, + // })); + const findAndGenerateSessionReplaceTime = taskSw.getElapsedAndReset(); - const [{ elapsed: emitSessionsReplaceTime }, { elapsed: emitPresenceUpdateTime }] = await Promise.all([ - timePromise(() => - emitEvent({ - event: "SESSIONS_REPLACE", - user_id: this.user_id, - data: allSessions, - } as SessionsReplace), - ), - timePromise(() => - emitEvent({ - event: "PRESENCE_UPDATE", - user_id: this.user_id, - data: { - user: user.toPublicUser(), - activities: this.session!.activities, - client_status: this.session!.client_status, - status: this.session!.getPublicStatus(), - }, - } as PresenceUpdateEvent), - ), - ]); + const [{ elapsed: emitSessionsReplaceTime }, { elapsed: emitPresenceUpdateTime }] = await Promise.all([ + timePromise(() => + emitEvent({ + event: "SESSIONS_REPLACE", + user_id: this.user_id, + data: allSessions, + } as SessionsReplace), + ), + timePromise(() => + emitEvent({ + event: "PRESENCE_UPDATE", + user_id: this.user_id, + data: { + user: user.toPublicUser(), + activities: this.session!.activities, + client_status: this.session!.client_status, + status: this.session!.getPublicStatus(), + }, + } as PresenceUpdateEvent), + ), + ]); - // Build READY + // Build READY - read_states.forEach((x) => { - x.id = x.channel_id; - }); - const remapReadStateIdsTime = taskSw.getElapsedAndReset(); + read_states.forEach((x) => { + x.id = x.channel_id; + }); + const remapReadStateIdsTime = taskSw.getElapsedAndReset(); - const d: ReadyEventData = { - v: 9, - application: application ? { id: application.id, flags: application.flags } : undefined, - user: user.toPrivateUser(["rights"]), - user_settings: user.settings, - user_settings_proto: settingsProtos?.userSettings ? PreloadedUserSettings.toBase64(settingsProtos.userSettings) : undefined, - user_settings_proto_json: settingsProtos?.userSettings ? PreloadedUserSettings.toJson(settingsProtos.userSettings) : undefined, - guilds: this.capabilities.has(Capabilities.FLAGS.CLIENT_STATE_V2) ? guilds.map((x) => new ReadyGuildDTO(x).toJSON()) : guilds, - relationships: user.relationships.map((x) => x.toPublicRelationship()), - read_state: { - entries: read_states, - partial: false, - version: 0, // TODO - }, - user_guild_settings: { - entries: user_guild_settings_entries, - partial: false, - version: 0, // TODO - }, - private_channels: channels, - presences: [], // TODO: Send actual data - session_id: this.session_id, - country_code: user.settings.locale, // TODO: do ip analysis instead - users: Array.from(users), - merged_members: merged_members, - sessions: allSessions, + const d: ReadyEventData = { + v: 9, + application: application ? { id: application.id, flags: application.flags } : undefined, + user: user.toPrivateUser(["rights"]), + user_settings: user.settings, + user_settings_proto: settingsProtos?.userSettings ? PreloadedUserSettings.toBase64(settingsProtos.userSettings) : undefined, + user_settings_proto_json: settingsProtos?.userSettings ? PreloadedUserSettings.toJson(settingsProtos.userSettings) : undefined, + guilds: this.capabilities.has(Capabilities.FLAGS.CLIENT_STATE_V2) ? guilds.map((x) => new ReadyGuildDTO(x).toJSON()) : guilds, + relationships: user.relationships.map((x) => x.toPublicRelationship()), + read_state: { + entries: read_states, + partial: false, + version: 0, // TODO + }, + user_guild_settings: { + entries: user_guild_settings_entries, + partial: false, + version: 0, // TODO + }, + private_channels: channels, + presences: [], // TODO: Send actual data + session_id: this.session_id, + country_code: user.settings.locale, // TODO: do ip analysis instead + users: Array.from(users), + merged_members: merged_members, + sessions: allSessions, - resume_gateway_url: Config.get().gateway.endpointPublic!, + resume_gateway_url: Config.get().gateway.endpointPublic!, - // lol hack whatever - required_action: Config.get().login.requireVerification && !user.verified ? "REQUIRE_VERIFIED_EMAIL" : undefined, + // lol hack whatever + required_action: Config.get().login.requireVerification && !user.verified ? "REQUIRE_VERIFIED_EMAIL" : undefined, - consents: { - personalization: { - consented: false, // TODO - }, - }, - experiments: [], - guild_join_requests: [], - connected_accounts: [], - guild_experiments: [], - geo_ordered_rtc_regions: [], - api_code_version: 1, - friend_suggestion_count: 0, - analytics_token: "", - tutorial: null, - session_type: "normal", // TODO - auth_session_id_hash: this.session.getDiscordDeviceInfo().id_hash, - notification_settings: { - // ???? - flags: 0, - }, - game_relationships: [], - }; + consents: { + personalization: { + consented: false, // TODO + }, + }, + experiments: [], + guild_join_requests: [], + connected_accounts: [], + guild_experiments: [], + geo_ordered_rtc_regions: [], + api_code_version: 1, + friend_suggestion_count: 0, + analytics_token: "", + tutorial: null, + session_type: "normal", // TODO + auth_session_id_hash: this.session.getDiscordDeviceInfo().id_hash, + notification_settings: { + // ???? + flags: 0, + }, + game_relationships: [], + }; - if (this.capabilities.has(Capabilities.FLAGS.AUTH_TOKEN_REFRESH) && tokenData.tokenVersion != CurrentTokenFormatVersion) { - d.auth_token = await generateToken(this.user_id); - } - const buildReadyEventDataTime = taskSw.getElapsedAndReset(); - const _trace = [ - gatewayShardName, - { - micros: totalSw.elapsed().totalMicroseconds, - calls: [], - }, - ] as TraceRoot; - const times = { - parseAndValidateTime, - userQueryTime, - validateIntentsAndShardingTime, - createSessionTime, - totalQueryTime, - createUserSettingsTime, - mergedMembersTime, - generateGuildsListTime, - generateUserGuildSettingsTime, - generateDmChannelsTime, - appendRelationshipsTime, - findAndGenerateSessionReplaceTime, - emitSessionsReplaceTime, - emitPresenceUpdateTime, - remapReadStateIdsTime, - buildReadyEventDataTime, - }; - for (const [key, value] of Object.entries(times)) { - if (value) { - const val = { micros: value.totalMicroseconds } as { micros: number; calls: TraceNode[] }; - _trace![1].calls.push(key, val); - if (key === "totalQueryTime") { - val.calls = []; - for (const [subkey, subvalue] of Object.entries({ - sessionSaveTime, - applicationQueryTime, - read_statesQueryTime, - membersQueryTime, - recipientsQueryTime, - queryGuildsTime, - queryGuildChannelsTime, - queryGuildEmojisTime, - queryGuildRolesTime, - queryGuildStickersTime, - queryGuildVoiceStatesTime, - })) { - if (subvalue) { - val.calls.push(subkey, { - micros: subvalue.totalMicroseconds, - } as TraceNode); - } - } + if (this.capabilities.has(Capabilities.FLAGS.AUTH_TOKEN_REFRESH) && tokenData.tokenVersion != CurrentTokenFormatVersion) { + d.auth_token = await generateToken(this.user_id); + } + const buildReadyEventDataTime = taskSw.getElapsedAndReset(); + const _trace = [ + gatewayShardName, + { + micros: totalSw.elapsed().totalMicroseconds, + calls: [], + }, + ] as TraceRoot; + const times = { + parseAndValidateTime, + userQueryTime, + validateIntentsAndShardingTime, + createSessionTime, + totalQueryTime, + createUserSettingsTime, + mergedMembersTime, + generateGuildsListTime, + generateUserGuildSettingsTime, + generateDmChannelsTime, + appendRelationshipsTime, + findAndGenerateSessionReplaceTime, + emitSessionsReplaceTime, + emitPresenceUpdateTime, + remapReadStateIdsTime, + buildReadyEventDataTime, + }; + for (const [key, value] of Object.entries(times)) { + if (value) { + const val = { micros: value.totalMicroseconds } as { micros: number; calls: TraceNode[] }; + _trace![1].calls.push(key, val); + if (key === "totalQueryTime") { + val.calls = []; + for (const [subkey, subvalue] of Object.entries({ + sessionSaveTime, + applicationQueryTime, + read_statesQueryTime, + membersQueryTime, + recipientsQueryTime, + queryGuildsTime, + queryGuildChannelsTime, + queryGuildEmojisTime, + queryGuildRolesTime, + queryGuildStickersTime, + queryGuildVoiceStatesTime, + })) { + if (subvalue) { + val.calls.push(subkey, { + micros: subvalue.totalMicroseconds, + } as TraceNode); + } + } - val.calls.push("mergeMemberGuildsTrace", mergeMemberGuildsTrace); - } - } - } - _trace![1].calls.push("buildTraceTime", { - micros: taskSw.elapsed().totalMicroseconds, - }); - d._trace = [JSON.stringify(_trace)]; + val.calls.push("mergeMemberGuildsTrace", mergeMemberGuildsTrace); + } + } + } + _trace![1].calls.push("buildTraceTime", { + micros: taskSw.elapsed().totalMicroseconds, + }); + d._trace = [JSON.stringify(_trace)]; - // Send READY - await Send(this, { - op: OPCODES.Dispatch, - t: EVENTEnum.Ready, - s: this.sequence++, - d, - }); + // Send READY + await Send(this, { + op: OPCODES.Dispatch, + t: EVENTEnum.Ready, + s: this.sequence++, + d, + }); - const readyTime = Date.now(); + const readyTime = Date.now(); - // If we're a bot user, send GUILD_CREATE for each unavailable guild - // TODO: check if bot has permission to view some of these based on intents (i.e. GUILD_MEMBERS, GUILD_PRESENCES, GUILD_VOICE_STATES) - await Promise.all( - pending_guilds.map((x) => { - //Even with the GUILD_MEMBERS intent, the bot always receives just itself as the guild members - const botMemberObject = members.find((member) => member.guild_id === x.id); + // If we're a bot user, send GUILD_CREATE for each unavailable guild + // TODO: check if bot has permission to view some of these based on intents (i.e. GUILD_MEMBERS, GUILD_PRESENCES, GUILD_VOICE_STATES) + await Promise.all( + pending_guilds.map((x) => { + //Even with the GUILD_MEMBERS intent, the bot always receives just itself as the guild members + const botMemberObject = members.find((member) => member.guild_id === x.id); - return Send(this, { - op: OPCODES.Dispatch, - t: EVENTEnum.GuildCreate, - s: this.sequence++, - d: { - ...x.toJSON(), - members: botMemberObject - ? [ - { - ...botMemberObject.toPublicMember(), - user: user.toPublicUser(), - }, - ] - : [], - }, - })?.catch((e) => console.error(`[Gateway] error when sending bot guilds`, e)); - }), - ); + return Send(this, { + op: OPCODES.Dispatch, + t: EVENTEnum.GuildCreate, + s: this.sequence++, + d: { + ...x.toJSON(), + members: botMemberObject + ? [ + { + ...botMemberObject.toPublicMember(), + user: user.toPublicUser(), + }, + ] + : [], + }, + })?.catch((e) => console.error(`[Gateway] error when sending bot guilds`, e)); + }), + ); - const pendingGuildsTime = Date.now(); + const pendingGuildsTime = Date.now(); - const readySupplementalGuilds = (guilds.filter((guild) => !guild.unavailable) as Guild[]).map((guild) => { - return { - voice_states: guild.voice_states.map((state) => state.toPublicVoiceState()), - id: guild.id, - embedded_activities: [], - }; - }); + const readySupplementalGuilds = (guilds.filter((guild) => !guild.unavailable) as Guild[]).map((guild) => { + return { + voice_states: guild.voice_states.map((state) => state.toPublicVoiceState()), + id: guild.id, + embedded_activities: [], + }; + }); - // TODO: ready supplemental - await Send(this, { - op: OPCodes.DISPATCH, - t: EVENTEnum.ReadySupplemental, - s: this.sequence++, - d: { - merged_presences: { - guilds: [], - friends: [], - }, - // these merged members seem to be all users currently in vc in your guilds - merged_members: [], - lazy_private_channels: [], - guilds: readySupplementalGuilds, // { voice_states: [], id: string, embedded_activities: [] } - // embedded_activities are users currently in an activity? - disclose: [], // Config.get().general.uniqueUsernames ? ["pomelo"] : [] - }, - }); + // TODO: ready supplemental + await Send(this, { + op: OPCodes.DISPATCH, + t: EVENTEnum.ReadySupplemental, + s: this.sequence++, + d: { + merged_presences: { + guilds: [], + friends: [], + }, + // these merged members seem to be all users currently in vc in your guilds + merged_members: [], + lazy_private_channels: [], + guilds: readySupplementalGuilds, // { voice_states: [], id: string, embedded_activities: [] } + // embedded_activities are users currently in an activity? + disclose: [], // Config.get().general.uniqueUsernames ? ["pomelo"] : [] + }, + }); - const readySupplementalTime = Date.now(); + const readySupplementalTime = Date.now(); - //TODO send GUILD_MEMBER_LIST_UPDATE - //TODO send VOICE_STATE_UPDATE to let the client know if another device is already connected to a voice channel + //TODO send GUILD_MEMBER_LIST_UPDATE + //TODO send VOICE_STATE_UPDATE to let the client know if another device is already connected to a voice channel - await setupListener.call(this); + await setupListener.call(this); - const setupListenerTime = Date.now(); + const setupListenerTime = Date.now(); - console.log(`[Gateway] IDENTIFY ${this.user_id} in ${totalSw.elapsed().totalMilliseconds}ms`, process.env.LOG_GATEWAY_TRACES ? JSON.stringify(d._trace, null, 2) : ""); + console.log(`[Gateway] IDENTIFY ${this.user_id} in ${totalSw.elapsed().totalMilliseconds}ms`, process.env.LOG_GATEWAY_TRACES ? JSON.stringify(d._trace, null, 2) : ""); } diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts index 6751a69..d994af5 100644 --- a/src/gateway/opcodes/LazyRequest.ts +++ b/src/gateway/opcodes/LazyRequest.ts @@ -27,252 +27,252 @@ // TODO: rewrite typeorm const getMostRelevantSession = (sessions: Session[]) => { - const statusMap = { - online: 0, - idle: 1, - dnd: 2, - invisible: 3, - offline: 4, - }; - // sort sessions by relevance - sessions = sessions.sort((a, b) => { - return statusMap[a.status] - statusMap[b.status] + ((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2; - }); + const statusMap = { + online: 0, + idle: 1, + dnd: 2, + invisible: 3, + offline: 4, + }; + // sort sessions by relevance + sessions = sessions.sort((a, b) => { + return statusMap[a.status] - statusMap[b.status] + ((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2; + }); - return sessions[0]; + return sessions[0]; }; async function getMembers(guild_id: string, range: [number, number]) { - if (!Array.isArray(range) || range.length !== 2) { - throw new Error("range is not a valid array"); - } + if (!Array.isArray(range) || range.length !== 2) { + throw new Error("range is not a valid array"); + } - let members: Member[] = []; - try { - members = - (await getDatabase() - ?.getRepository(Member) - .createQueryBuilder("member") - .where("member.guild_id = :guild_id", { guild_id }) - .leftJoinAndSelect("member.roles", "role") - .leftJoinAndSelect("member.user", "user") - .leftJoinAndSelect("user.sessions", "session") - .addSelect("user.settings") - .addSelect("CASE WHEN session.status IS NULL OR session.status = 'offline' OR session.status = 'invisible' THEN 0 ELSE 1 END", "_status") - .orderBy("_status", "DESC") - .addOrderBy("role.position", "DESC") - .addOrderBy("user.username", "ASC") - .offset(Number(range[0]) || 0) - .limit(Number(range[1]) || 100) - .getMany()) ?? []; - } catch (e) { - console.error(`LazyRequest`, e); - } + let members: Member[] = []; + try { + members = + (await getDatabase() + ?.getRepository(Member) + .createQueryBuilder("member") + .where("member.guild_id = :guild_id", { guild_id }) + .leftJoinAndSelect("member.roles", "role") + .leftJoinAndSelect("member.user", "user") + .leftJoinAndSelect("user.sessions", "session") + .addSelect("user.settings") + .addSelect("CASE WHEN session.status IS NULL OR session.status = 'offline' OR session.status = 'invisible' THEN 0 ELSE 1 END", "_status") + .orderBy("_status", "DESC") + .addOrderBy("role.position", "DESC") + .addOrderBy("user.username", "ASC") + .offset(Number(range[0]) || 0) + .limit(Number(range[1]) || 100) + .getMany()) ?? []; + } catch (e) { + console.error(`LazyRequest`, e); + } - if (!members || !members.length) { - return { - items: [], - groups: [], - range: [], - members: [], - }; - } + if (!members || !members.length) { + return { + items: [], + groups: [], + range: [], + members: [], + }; + } - const groups = []; - const items = []; - const member_roles = [ - ...new Map( - members - .map((m) => m.roles) - .flat() - .map((role) => [role.id, role] as [string, Role]), - ).values(), - ]; - member_roles.push( - member_roles.splice( - member_roles.findIndex((x) => x.id === x.guild_id), - 1, - )[0], - ); + const groups = []; + const items = []; + const member_roles = [ + ...new Map( + members + .map((m) => m.roles) + .flat() + .map((role) => [role.id, role] as [string, Role]), + ).values(), + ]; + member_roles.push( + member_roles.splice( + member_roles.findIndex((x) => x.id === x.guild_id), + 1, + )[0], + ); - const offlineItems = []; + const offlineItems = []; - for (const role of member_roles) { - const [role_members, other_members] = arrayPartition(members, (m: Member) => !!m.roles.find((r) => r.id === role.id)); - const group = { - count: role_members.length, - id: role.id === guild_id ? "online" : role.id, - }; + for (const role of member_roles) { + const [role_members, other_members] = arrayPartition(members, (m: Member) => !!m.roles.find((r) => r.id === role.id)); + const group = { + count: role_members.length, + id: role.id === guild_id ? "online" : role.id, + }; - items.push({ group }); - groups.push(group); + items.push({ group }); + groups.push(group); - for (const member of role_members) { - const roles = member.roles.filter((x: Role) => x.id !== guild_id).map((x: Role) => x.id); + for (const member of role_members) { + const roles = member.roles.filter((x: Role) => x.id !== guild_id).map((x: Role) => x.id); - const session: Session | undefined = getMostRelevantSession(member.user.sessions); + const session: Session | undefined = getMostRelevantSession(member.user.sessions); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (session?.status == "unknown") { - session.status = member?.user?.settings?.status || "online"; - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (session?.status == "unknown") { + session.status = member?.user?.settings?.status || "online"; + } - const item = { - member: { - ...member, - roles, - user: member.user.toPublicUser(), - presence: { - ...session, - activities: session?.activities || [], - user: { id: member.user.id }, - }, - }, - }; + const item = { + member: { + ...member, + roles, + user: member.user.toPublicUser(), + presence: { + ...session, + activities: session?.activities || [], + user: { id: member.user.id }, + }, + }, + }; - if (!session || session.status == "invisible" || session.status == "offline") { - item.member.presence.status = "offline"; - offlineItems.push(item); - group.count--; - continue; - } + if (!session || session.status == "invisible" || session.status == "offline") { + item.member.presence.status = "offline"; + offlineItems.push(item); + group.count--; + continue; + } - items.push(item); - } - members = other_members; - } + items.push(item); + } + members = other_members; + } - if (offlineItems.length) { - const group = { - count: offlineItems.length, - id: "offline", - }; - items.push({ group }); - groups.push(group); + if (offlineItems.length) { + const group = { + count: offlineItems.length, + id: "offline", + }; + items.push({ group }); + groups.push(group); - items.push(...offlineItems); - } + items.push(...offlineItems); + } - return { - items, - groups, - range, - members: items.map((x) => ("member" in x ? { ...x.member, settings: undefined } : undefined)).filter((x) => !!x), - }; + return { + items, + groups, + range, + members: items.map((x) => ("member" in x ? { ...x.member, settings: undefined } : undefined)).filter((x) => !!x), + }; } async function subscribeToMemberEvents(this: WebSocket, user_id: string) { - if (this.events[user_id]) return false; // already subscribed as friend - if (this.member_events[user_id]) return false; // already subscribed in member list - this.member_events[user_id] = await listenEvent(user_id, handlePresenceUpdate.bind(this), this.listen_options); - return true; + if (this.events[user_id]) return false; // already subscribed as friend + if (this.member_events[user_id]) return false; // already subscribed in member list + this.member_events[user_id] = await listenEvent(user_id, handlePresenceUpdate.bind(this), this.listen_options); + return true; } export async function onLazyRequest(this: WebSocket, { d }: Payload) { - const startTime = Date.now(); - // TODO: check data - check.call(this, LazyRequestSchema, d); - const { guild_id, typing, channels, activities, members } = d as LazyRequestSchema; + const startTime = Date.now(); + // TODO: check data + check.call(this, LazyRequestSchema, d); + const { guild_id, typing, channels, activities, members } = d as LazyRequestSchema; - if (members) { - // Client has requested a PRESENCE_UPDATE for specific member + if (members) { + // Client has requested a PRESENCE_UPDATE for specific member - await Promise.all([ - members.map(async (x) => { - if (!x) return; - const didSubscribe = await subscribeToMemberEvents.call(this, x); - if (!didSubscribe) return; + await Promise.all([ + members.map(async (x) => { + if (!x) return; + const didSubscribe = await subscribeToMemberEvents.call(this, x); + if (!didSubscribe) return; - // if we didn't subscribe just now, this is a new subscription - // and we should send a PRESENCE_UPDATE immediately + // if we didn't subscribe just now, this is a new subscription + // and we should send a PRESENCE_UPDATE immediately - const sessions = await Session.find({ where: { user_id: x } }); - const session = getMostRelevantSession(sessions); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (session?.status == "unknown") session.status = "online"; - const user = (await User.getPublicUser(x)).toPublicUser(); // why is this needed? + const sessions = await Session.find({ where: { user_id: x } }); + const session = getMostRelevantSession(sessions); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (session?.status == "unknown") session.status = "online"; + const user = (await User.getPublicUser(x)).toPublicUser(); // why is this needed? - return Send(this, { - op: OPCODES.Dispatch, - s: this.sequence++, - t: "PRESENCE_UPDATE", - d: { - user: user, - activities: session?.activities || [], - client_status: session?.client_status, - status: session?.getPublicStatus() || "offline", - } as Presence, - }); - }), - ]); + return Send(this, { + op: OPCODES.Dispatch, + s: this.sequence++, + t: "PRESENCE_UPDATE", + d: { + user: user, + activities: session?.activities || [], + client_status: session?.client_status, + status: session?.getPublicStatus() || "offline", + } as Presence, + }); + }), + ]); - if (!channels) return; - } + if (!channels) return; + } - if (!channels) throw new Error("Must provide channel ranges"); + if (!channels) throw new Error("Must provide channel ranges"); - const channel_id = Object.keys(channels || {})[0]; - if (!channel_id) return; + const channel_id = Object.keys(channels || {})[0]; + if (!channel_id) return; - const permissions = await getPermission(this.user_id, guild_id, channel_id); - permissions.hasThrow("VIEW_CHANNEL"); + const permissions = await getPermission(this.user_id, guild_id, channel_id); + permissions.hasThrow("VIEW_CHANNEL"); - const ranges = channels[channel_id]; - if (!Array.isArray(ranges)) throw new Error("Not a valid Array"); + const ranges = channels[channel_id]; + if (!Array.isArray(ranges)) throw new Error("Not a valid Array"); - const member_count = await Member.count({ where: { guild_id } }); - const ops = await Promise.all(ranges.map((x) => getMembers(guild_id, x as [number, number]))); + const member_count = await Member.count({ where: { guild_id } }); + const ops = await Promise.all(ranges.map((x) => getMembers(guild_id, x as [number, number]))); - let list_id = "everyone"; + let list_id = "everyone"; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (channel.permission_overwrites) { - const perms: string[] = []; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (channel.permission_overwrites) { + const perms: string[] = []; - channel.permission_overwrites.forEach((overwrite) => { - const { id, allow, deny } = overwrite; + channel.permission_overwrites.forEach((overwrite) => { + const { id, allow, deny } = overwrite; - if (BigInt(allow) & Permissions.FLAGS.VIEW_CHANNEL) perms.push(`allow:${id}`); - else if (BigInt(deny) & Permissions.FLAGS.VIEW_CHANNEL) perms.push(`deny:${id}`); - }); + if (BigInt(allow) & Permissions.FLAGS.VIEW_CHANNEL) perms.push(`allow:${id}`); + else if (BigInt(deny) & Permissions.FLAGS.VIEW_CHANNEL) perms.push(`deny:${id}`); + }); - if (perms.length > 0) { - list_id = murmur(perms.sort().join(",")).toString(); - } - } + if (perms.length > 0) { + list_id = murmur(perms.sort().join(",")).toString(); + } + } - // TODO: unsubscribe member_events that are not in op.members + // TODO: unsubscribe member_events that are not in op.members - ops.forEach((op) => { - op.members.forEach(async (member) => { - if (!member?.user.id) return; - return subscribeToMemberEvents.call(this, member.user.id); - }); - }); + ops.forEach((op) => { + op.members.forEach(async (member) => { + if (!member?.user.id) return; + return subscribeToMemberEvents.call(this, member.user.id); + }); + }); - const groups = [...new Set(ops.map((x) => x.groups).flat())]; + const groups = [...new Set(ops.map((x) => x.groups).flat())]; - await Send(this, { - op: OPCODES.Dispatch, - s: this.sequence++, - t: "GUILD_MEMBER_LIST_UPDATE", - d: { - ops: ops.map((x) => ({ - items: x.items, - op: "SYNC", - range: x.range, - })), - online_count: member_count - (groups.find((x) => x.id == "offline")?.count ?? 0), - member_count, - id: list_id, - guild_id, - groups, - }, - }); + await Send(this, { + op: OPCODES.Dispatch, + s: this.sequence++, + t: "GUILD_MEMBER_LIST_UPDATE", + d: { + ops: ops.map((x) => ({ + items: x.items, + op: "SYNC", + range: x.range, + })), + online_count: member_count - (groups.find((x) => x.id == "offline")?.count ?? 0), + member_count, + id: list_id, + guild_id, + groups, + }, + }); - console.log(`[Gateway] LAZY_REQUEST ${guild_id} ${channel_id} took ${Date.now() - startTime}ms`); + console.log(`[Gateway] LAZY_REQUEST ${guild_id} ${channel_id} took ${Date.now() - startTime}ms`); } diff --git a/src/gateway/opcodes/PresenceUpdate.ts b/src/gateway/opcodes/PresenceUpdate.ts index 92bd3e2..2dfa861 100644 --- a/src/gateway/opcodes/PresenceUpdate.ts +++ b/src/gateway/opcodes/PresenceUpdate.ts @@ -22,27 +22,27 @@ import { ActivitySchema } from "@spacebar/schemas"; export async function onPresenceUpdate(this: WebSocket, { d }: Payload) { - const startTime = Date.now(); - check.call(this, ActivitySchema, d); - const presence = d as ActivitySchema; + const startTime = Date.now(); + check.call(this, ActivitySchema, d); + const presence = d as ActivitySchema; - await Session.update({ session_id: this.session_id }, { status: presence.status, activities: presence.activities }); + await Session.update({ session_id: this.session_id }, { status: presence.status, activities: presence.activities }); - const session = await Session.findOneOrFail({ - select: ["client_status"], - where: { session_id: this.session_id }, - }); + const session = await Session.findOneOrFail({ + select: ["client_status"], + where: { session_id: this.session_id }, + }); - await emitEvent({ - event: "PRESENCE_UPDATE", - user_id: this.user_id, - data: { - user: await User.getPublicUser(this.user_id), - status: session.getPublicStatus(), - activities: presence.activities, - client_status: session.client_status, - }, - } as PresenceUpdateEvent); + await emitEvent({ + event: "PRESENCE_UPDATE", + user_id: this.user_id, + data: { + user: await User.getPublicUser(this.user_id), + status: session.getPublicStatus(), + activities: presence.activities, + client_status: session.client_status, + }, + } as PresenceUpdateEvent); - console.log(`Presence update for user ${this.user_id} processed in ${Date.now() - startTime}ms`); + console.log(`Presence update for user ${this.user_id} processed in ${Date.now() - startTime}ms`); } diff --git a/src/gateway/opcodes/RequestChannelStatuses.ts b/src/gateway/opcodes/RequestChannelStatuses.ts index e3fefd1..507cfd5 100644 --- a/src/gateway/opcodes/RequestChannelStatuses.ts +++ b/src/gateway/opcodes/RequestChannelStatuses.ts @@ -19,17 +19,17 @@ import { WebSocket, Payload, OPCODES, Send } from "@spacebar/gateway"; export async function onRequestChannelStatuses(this: WebSocket, { d }: Payload) { - const startTime = Date.now(); - // Schema validation can only accept either string or array, so transforming it here to support both - if (!d.guild_id) throw new Error('"guild_id" is required'); + const startTime = Date.now(); + // Schema validation can only accept either string or array, so transforming it here to support both + if (!d.guild_id) throw new Error('"guild_id" is required'); - // TODO: implement - await Send(this, { - op: OPCODES.Dispatch, - t: "CHANNEL_STATUSES", - d: { - guild_id: d.guild_id, - channels: [], - }, - }); + // TODO: implement + await Send(this, { + op: OPCODES.Dispatch, + t: "CHANNEL_STATUSES", + d: { + guild_id: d.guild_id, + channels: [], + }, + }); } diff --git a/src/gateway/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts index 9807e3d..1c70809 100644 --- a/src/gateway/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts @@ -23,166 +23,166 @@ import { RequestGuildMembersSchema } from "@spacebar/schemas"; export async function onRequestGuildMembers(this: WebSocket, { d }: Payload) { - const startTime = Date.now(); - // Schema validation can only accept either string or array, so transforming it here to support both - if (!d.guild_id) throw new Error('"guild_id" is required'); - d.guild_id = Array.isArray(d.guild_id) ? d.guild_id[0] : d.guild_id; + const startTime = Date.now(); + // Schema validation can only accept either string or array, so transforming it here to support both + if (!d.guild_id) throw new Error('"guild_id" is required'); + d.guild_id = Array.isArray(d.guild_id) ? d.guild_id[0] : d.guild_id; - if (d.user_ids && !Array.isArray(d.user_ids)) d.user_ids = [d.user_ids]; + if (d.user_ids && !Array.isArray(d.user_ids)) d.user_ids = [d.user_ids]; - check.call(this, RequestGuildMembersSchema, d); + check.call(this, RequestGuildMembersSchema, d); - const { presences, nonce, query: requestQuery } = d as RequestGuildMembersSchema; - let { limit, user_ids, guild_id } = d as RequestGuildMembersSchema; + const { presences, nonce, query: requestQuery } = d as RequestGuildMembersSchema; + let { limit, user_ids, guild_id } = d as RequestGuildMembersSchema; - // some discord libraries send empty string as query when they meant to send undefined, which was leading to errors being thrown in this handler - const query = requestQuery != "" ? requestQuery : undefined; + // some discord libraries send empty string as query when they meant to send undefined, which was leading to errors being thrown in this handler + const query = requestQuery != "" ? requestQuery : undefined; - guild_id = guild_id as string; - user_ids = user_ids as string[] | undefined; + guild_id = guild_id as string; + user_ids = user_ids as string[] | undefined; - if (d.query && (!limit || Number.isNaN(limit))) { - console.log("Query:", d); - throw new Error('"query" requires "limit" to be set'); - } + if (d.query && (!limit || Number.isNaN(limit))) { + console.log("Query:", d); + throw new Error('"query" requires "limit" to be set'); + } - if (d.query && user_ids) { - console.log("Query:", d); - throw new Error('"query" and "user_ids" are mutually exclusive'); - } + if (d.query && user_ids) { + console.log("Query:", d); + throw new Error('"query" and "user_ids" are mutually exclusive'); + } - // TODO: Configurable limit? - if ((query || (user_ids && user_ids.length > 0)) && (!limit || limit > 100)) limit = 100; + // TODO: Configurable limit? + if ((query || (user_ids && user_ids.length > 0)) && (!limit || limit > 100)) limit = 100; - const permissions = await getPermission(this.user_id, guild_id); - permissions.hasThrow("VIEW_CHANNEL"); + const permissions = await getPermission(this.user_id, guild_id); + permissions.hasThrow("VIEW_CHANNEL"); - const memberCount = await Member.count({ - where: { - guild_id, - }, - }); + const memberCount = await Member.count({ + where: { + guild_id, + }, + }); - const memberFind: FindManyOptions = { - where: { - guild_id, - }, - relations: ["user", "roles"], - }; - if (limit) memberFind.take = Math.abs(Number(limit || 100)); + const memberFind: FindManyOptions = { + where: { + guild_id, + }, + relations: ["user", "roles"], + }; + if (limit) memberFind.take = Math.abs(Number(limit || 100)); - let members: Member[] = []; + let members: Member[] = []; - if (memberCount > 75000) { - // since we dont have voice channels yet, just return the connecting users member object - members = await Member.find({ - ...memberFind, - where: { - ...memberFind.where, - user: { - id: this.user_id, - }, - }, - }); - } else if (memberCount > this.large_threshold) { - // find all members who are online, have a role, have a nickname, or are in a voice channel, as well as respecting the query and user_ids - const db = getDatabase(); - if (!db) throw new Error("Database not initialized"); - const repo = db.getRepository(Member); - const q = repo - .createQueryBuilder("member") - .where("member.guild_id = :guild_id", { guild_id }) - .leftJoinAndSelect("member.roles", "role") - .leftJoinAndSelect("member.user", "user") - .leftJoinAndSelect("user.sessions", "session") - .andWhere("',' || member.roles || ',' NOT LIKE :everyoneRoleIdList", { everyoneRoleIdList: "%," + guild_id + ",%" }) - .addOrderBy("user.username", "ASC") - .limit(memberFind.take); + if (memberCount > 75000) { + // since we dont have voice channels yet, just return the connecting users member object + members = await Member.find({ + ...memberFind, + where: { + ...memberFind.where, + user: { + id: this.user_id, + }, + }, + }); + } else if (memberCount > this.large_threshold) { + // find all members who are online, have a role, have a nickname, or are in a voice channel, as well as respecting the query and user_ids + const db = getDatabase(); + if (!db) throw new Error("Database not initialized"); + const repo = db.getRepository(Member); + const q = repo + .createQueryBuilder("member") + .where("member.guild_id = :guild_id", { guild_id }) + .leftJoinAndSelect("member.roles", "role") + .leftJoinAndSelect("member.user", "user") + .leftJoinAndSelect("user.sessions", "session") + .andWhere("',' || member.roles || ',' NOT LIKE :everyoneRoleIdList", { everyoneRoleIdList: "%," + guild_id + ",%" }) + .addOrderBy("user.username", "ASC") + .limit(memberFind.take); - if (query && query != "") { - q.andWhere(`user.username ILIKE :query`, { - query: `${query}%`, - }); - } else if (user_ids) { - q.andWhere(`user.id IN (:...user_ids)`, { user_ids }); - } + if (query && query != "") { + q.andWhere(`user.username ILIKE :query`, { + query: `${query}%`, + }); + } else if (user_ids) { + q.andWhere(`user.id IN (:...user_ids)`, { user_ids }); + } - members = await q.getMany(); - } else { - if (query) { - // @ts-expect-error memberFind.where is very much defined - memberFind.where.user = { - username: ILike(query + "%"), - }; - } else if (user_ids && user_ids.length > 0) { - // @ts-expect-error memberFind.where is still very much defined - memberFind.where.id = In(user_ids); - } + members = await q.getMany(); + } else { + if (query) { + // @ts-expect-error memberFind.where is very much defined + memberFind.where.user = { + username: ILike(query + "%"), + }; + } else if (user_ids && user_ids.length > 0) { + // @ts-expect-error memberFind.where is still very much defined + memberFind.where.id = In(user_ids); + } - members = await Member.find(memberFind); - } + members = await Member.find(memberFind); + } - const baseData = { - guild_id, - nonce, - }; + const baseData = { + guild_id, + nonce, + }; - const chunkCount = Math.ceil(members.length / 1000); + const chunkCount = Math.ceil(members.length / 1000); - let notFound: string[] = []; - if (user_ids && user_ids.length > 0) notFound = user_ids.filter((id) => !members.some((member) => member.id == id)); + let notFound: string[] = []; + if (user_ids && user_ids.length > 0) notFound = user_ids.filter((id) => !members.some((member) => member.id == id)); - const chunks: GuildMembersChunkEvent["data"][] = []; - while (members.length > 0) { - const chunk: Member[] = members.splice(0, 1000); + const chunks: GuildMembersChunkEvent["data"][] = []; + while (members.length > 0) { + const chunk: Member[] = members.splice(0, 1000); - const presenceList: Presence[] = []; - if (presences) { - for await (const member of chunk) { - const session = await Session.findOne({ - where: { user_id: member.id }, - }); - if (session) - presenceList.push({ - user: member.user.toPublicUser(), - status: session.status, - activities: session.activities, - client_status: session.client_status, - }); - } - } + const presenceList: Presence[] = []; + if (presences) { + for await (const member of chunk) { + const session = await Session.findOne({ + where: { user_id: member.id }, + }); + if (session) + presenceList.push({ + user: member.user.toPublicUser(), + status: session.status, + activities: session.activities, + client_status: session.client_status, + }); + } + } - chunks.push({ - ...baseData, - members: chunk.map((member) => member.toPublicMember()), - presences: presences ? presenceList : undefined, - chunk_index: chunks.length, - chunk_count: chunkCount, - }); - } + chunks.push({ + ...baseData, + members: chunk.map((member) => member.toPublicMember()), + presences: presences ? presenceList : undefined, + chunk_index: chunks.length, + chunk_count: chunkCount, + }); + } - if (chunks.length == 0) { - chunks.push({ - ...baseData, - members: [], - presences: presences ? [] : undefined, - chunk_index: 0, - chunk_count: 1, - }); - } + if (chunks.length == 0) { + chunks.push({ + ...baseData, + members: [], + presences: presences ? [] : undefined, + chunk_index: 0, + chunk_count: 1, + }); + } - if (notFound.length > 0) { - chunks[0].not_found = notFound; - } + if (notFound.length > 0) { + chunks[0].not_found = notFound; + } - chunks.forEach((chunk) => { - Send(this, { - op: OPCODES.Dispatch, - s: this.sequence++, - t: "GUILD_MEMBERS_CHUNK", - d: chunk, - }); - }); + chunks.forEach((chunk) => { + Send(this, { + op: OPCODES.Dispatch, + s: this.sequence++, + t: "GUILD_MEMBERS_CHUNK", + d: chunk, + }); + }); - console.log(`[Gateway] REQUEST_GUILD_MEMBERS took ${Date.now() - startTime}ms for guild ${guild_id} with ${members.length} members`); + console.log(`[Gateway] REQUEST_GUILD_MEMBERS took ${Date.now() - startTime}ms for guild ${guild_id} with ${members.length} members`); } diff --git a/src/gateway/opcodes/Resume.ts b/src/gateway/opcodes/Resume.ts index fc6d8a7..fc3cbe5 100644 --- a/src/gateway/opcodes/Resume.ts +++ b/src/gateway/opcodes/Resume.ts @@ -20,11 +20,11 @@ import { Send } from "../util/Send"; export async function onResume(this: WebSocket) { - // console.log("Got Resume -> cancel not implemented"); - await Send(this, { - op: 9, - d: false, - }); + // console.log("Got Resume -> cancel not implemented"); + await Send(this, { + op: 9, + d: false, + }); - // return this.close(CLOSECODES.Invalid_session); + // return this.close(CLOSECODES.Invalid_session); } diff --git a/src/gateway/opcodes/StreamCreate.ts b/src/gateway/opcodes/StreamCreate.ts index 00c8c94..176a084 100644 --- a/src/gateway/opcodes/StreamCreate.ts +++ b/src/gateway/opcodes/StreamCreate.ts @@ -1,115 +1,115 @@ import { genVoiceToken, Payload, WebSocket, generateStreamKey } from "@spacebar/gateway"; import { - Channel, - Config, - emitEvent, - Member, - Snowflake, - Stream, - StreamCreateEvent, - StreamServerUpdateEvent, - StreamSession, - VoiceState, - VoiceStateUpdateEvent, + Channel, + Config, + emitEvent, + Member, + Snowflake, + Stream, + StreamCreateEvent, + StreamServerUpdateEvent, + StreamSession, + VoiceState, + VoiceStateUpdateEvent, } from "@spacebar/util"; import { check } from "./instanceOf"; import { StreamCreateSchema } from "@spacebar/schemas"; export async function onStreamCreate(this: WebSocket, data: Payload) { - const startTime = Date.now(); - check.call(this, StreamCreateSchema, data.d); - const body = data.d as StreamCreateSchema; + const startTime = Date.now(); + check.call(this, StreamCreateSchema, data.d); + const body = data.d as StreamCreateSchema; - if (body.channel_id.trim().length === 0) return; + if (body.channel_id.trim().length === 0) return; - // first check if we are in a voice channel already. cannot create a stream if there's no existing voice connection - const voiceState = await VoiceState.findOne({ - where: { user_id: this.user_id }, - }); + // first check if we are in a voice channel already. cannot create a stream if there's no existing voice connection + const voiceState = await VoiceState.findOne({ + where: { user_id: this.user_id }, + }); - if (!voiceState || !voiceState.channel_id) return; + if (!voiceState || !voiceState.channel_id) return; - if (body.guild_id) { - voiceState.member = await Member.findOneOrFail({ - where: { id: voiceState.user_id, guild_id: voiceState.guild_id }, - relations: ["user", "roles"], - }); - } + if (body.guild_id) { + voiceState.member = await Member.findOneOrFail({ + where: { id: voiceState.user_id, guild_id: voiceState.guild_id }, + relations: ["user", "roles"], + }); + } - // TODO: permissions check - if it's a guild, check if user is allowed to create stream in this guild + // TODO: permissions check - if it's a guild, check if user is allowed to create stream in this guild - const channel = await Channel.findOne({ - where: { id: body.channel_id }, - }); + const channel = await Channel.findOne({ + where: { id: body.channel_id }, + }); - if (!channel || (body.type === "guild" && channel.guild_id != body.guild_id)) return this.close(4000, "invalid channel"); + if (!channel || (body.type === "guild" && channel.guild_id != body.guild_id)) return this.close(4000, "invalid channel"); - // TODO: actually apply preferred_region from the event payload - const regions = Config.get().regions; - const guildRegion = regions.available.filter((r) => r.id === regions.default)[0]; + // TODO: actually apply preferred_region from the event payload + const regions = Config.get().regions; + const guildRegion = regions.available.filter((r) => r.id === regions.default)[0]; - // first make sure theres no other streams for this user that somehow didnt get cleared - await Stream.delete({ - owner_id: this.user_id, - }); + // first make sure theres no other streams for this user that somehow didnt get cleared + await Stream.delete({ + owner_id: this.user_id, + }); - // create a new entry in db containing the token for authenticating user in stream gateway IDENTIFY - const stream = Stream.create({ - id: Snowflake.generate(), - owner_id: this.user_id, - channel_id: body.channel_id, - endpoint: guildRegion.endpoint, - }); + // create a new entry in db containing the token for authenticating user in stream gateway IDENTIFY + const stream = Stream.create({ + id: Snowflake.generate(), + owner_id: this.user_id, + channel_id: body.channel_id, + endpoint: guildRegion.endpoint, + }); - await stream.save(); + await stream.save(); - const token = genVoiceToken(); + const token = genVoiceToken(); - const streamSession = StreamSession.create({ - stream_id: stream.id, - user_id: this.user_id, - session_id: this.session_id, - token, - }); + const streamSession = StreamSession.create({ + stream_id: stream.id, + user_id: this.user_id, + session_id: this.session_id, + token, + }); - await streamSession.save(); + await streamSession.save(); - const streamKey = generateStreamKey(body.type, body.guild_id, body.channel_id, this.user_id); + const streamKey = generateStreamKey(body.type, body.guild_id, body.channel_id, this.user_id); - await emitEvent({ - event: "STREAM_CREATE", - data: { - stream_key: streamKey, - rtc_server_id: stream.id, // for voice connections in guilds it is guild_id, for dm voice calls it seems to be DM channel id, for GoLive streams a generated number - viewer_ids: [], - region: guildRegion.name, - paused: false, - }, - user_id: this.user_id, - } as StreamCreateEvent); + await emitEvent({ + event: "STREAM_CREATE", + data: { + stream_key: streamKey, + rtc_server_id: stream.id, // for voice connections in guilds it is guild_id, for dm voice calls it seems to be DM channel id, for GoLive streams a generated number + viewer_ids: [], + region: guildRegion.name, + paused: false, + }, + user_id: this.user_id, + } as StreamCreateEvent); - await emitEvent({ - event: "STREAM_SERVER_UPDATE", - data: { - token: streamSession.token, - stream_key: streamKey, - guild_id: null, // not sure why its always null - endpoint: stream.endpoint, - }, - user_id: this.user_id, - } as StreamServerUpdateEvent); + await emitEvent({ + event: "STREAM_SERVER_UPDATE", + data: { + token: streamSession.token, + stream_key: streamKey, + guild_id: null, // not sure why its always null + endpoint: stream.endpoint, + }, + user_id: this.user_id, + } as StreamServerUpdateEvent); - voiceState.self_stream = true; - await voiceState.save(); + voiceState.self_stream = true; + await voiceState.save(); - await emitEvent({ - event: "VOICE_STATE_UPDATE", - data: voiceState.toPublicVoiceState(), - guild_id: voiceState.guild_id, - channel_id: voiceState.channel_id, - } as VoiceStateUpdateEvent); + await emitEvent({ + event: "VOICE_STATE_UPDATE", + data: voiceState.toPublicVoiceState(), + guild_id: voiceState.guild_id, + channel_id: voiceState.channel_id, + } as VoiceStateUpdateEvent); - console.log(`[Gateway] STREAM_CREATE for user ${this.user_id} in channel ${body.channel_id} with stream key ${streamKey} in ${Date.now() - startTime}ms`); + console.log(`[Gateway] STREAM_CREATE for user ${this.user_id} in channel ${body.channel_id} with stream key ${streamKey} in ${Date.now() - startTime}ms`); } //stream key: diff --git a/src/gateway/opcodes/StreamDelete.ts b/src/gateway/opcodes/StreamDelete.ts index 34a3b9d..c684b6c 100644 --- a/src/gateway/opcodes/StreamDelete.ts +++ b/src/gateway/opcodes/StreamDelete.ts @@ -4,70 +4,70 @@ import { StreamDeleteSchema } from "@spacebar/schemas"; export async function onStreamDelete(this: WebSocket, data: Payload) { - const startTime = Date.now(); - check.call(this, StreamDeleteSchema, data.d); - const body = data.d as StreamDeleteSchema; + const startTime = Date.now(); + check.call(this, StreamDeleteSchema, data.d); + const body = data.d as StreamDeleteSchema; - let parsedKey: { - type: "guild" | "call"; - channelId: string; - guildId?: string; - userId: string; - }; + let parsedKey: { + type: "guild" | "call"; + channelId: string; + guildId?: string; + userId: string; + }; - try { - parsedKey = parseStreamKey(body.stream_key); - } catch (e) { - return this.close(4000, "Invalid stream key"); - } + try { + parsedKey = parseStreamKey(body.stream_key); + } catch (e) { + return this.close(4000, "Invalid stream key"); + } - const { userId, channelId, guildId, type } = parsedKey; + const { userId, channelId, guildId, type } = parsedKey; - // when a user selects to stop watching another user stream, this event gets triggered - // just disconnect user without actually deleting stream - if (this.user_id !== userId) { - await emitEvent({ - event: "STREAM_DELETE", - data: { - stream_key: body.stream_key, - }, - user_id: this.user_id, - } as StreamDeleteEvent); - return; - } + // when a user selects to stop watching another user stream, this event gets triggered + // just disconnect user without actually deleting stream + if (this.user_id !== userId) { + await emitEvent({ + event: "STREAM_DELETE", + data: { + stream_key: body.stream_key, + }, + user_id: this.user_id, + } as StreamDeleteEvent); + return; + } - const stream = await Stream.findOne({ - where: { channel_id: channelId, owner_id: userId }, - }); + const stream = await Stream.findOne({ + where: { channel_id: channelId, owner_id: userId }, + }); - if (!stream) return; + if (!stream) return; - await stream.remove(); + await stream.remove(); - const voiceState = await VoiceState.findOne({ - where: { user_id: this.user_id }, - }); + const voiceState = await VoiceState.findOne({ + where: { user_id: this.user_id }, + }); - if (voiceState) { - voiceState.self_stream = false; - await voiceState.save(); + if (voiceState) { + voiceState.self_stream = false; + await voiceState.save(); - await emitEvent({ - event: "VOICE_STATE_UPDATE", - data: voiceState.toPublicVoiceState(), - guild_id: guildId, - channel_id: channelId, - } as VoiceStateUpdateEvent); - } + await emitEvent({ + event: "VOICE_STATE_UPDATE", + data: voiceState.toPublicVoiceState(), + guild_id: guildId, + channel_id: channelId, + } as VoiceStateUpdateEvent); + } - await emitEvent({ - event: "STREAM_DELETE", - data: { - stream_key: body.stream_key, - }, - guild_id: guildId, - channel_id: channelId, - } as StreamDeleteEvent); + await emitEvent({ + event: "STREAM_DELETE", + data: { + stream_key: body.stream_key, + }, + guild_id: guildId, + channel_id: channelId, + } as StreamDeleteEvent); - console.log(`[Gateway] STREAM_DELETE for user ${this.user_id} in channel ${channelId} with stream key ${body.stream_key} in ${Date.now() - startTime}ms`); + console.log(`[Gateway] STREAM_DELETE for user ${this.user_id} in channel ${channelId} with stream key ${body.stream_key} in ${Date.now() - startTime}ms`); } diff --git a/src/gateway/opcodes/StreamWatch.ts b/src/gateway/opcodes/StreamWatch.ts index 3bb1ce7..4e51865 100644 --- a/src/gateway/opcodes/StreamWatch.ts +++ b/src/gateway/opcodes/StreamWatch.ts @@ -5,82 +5,82 @@ import { StreamWatchSchema } from "@spacebar/schemas"; export async function onStreamWatch(this: WebSocket, data: Payload) { - const startTime = Date.now(); - check.call(this, StreamWatchSchema, data.d); - const body = data.d as StreamWatchSchema; + const startTime = Date.now(); + check.call(this, StreamWatchSchema, data.d); + const body = data.d as StreamWatchSchema; - // TODO: apply perms: check if user is allowed to watch + // TODO: apply perms: check if user is allowed to watch - let parsedKey: { - type: "guild" | "call"; - channelId: string; - guildId?: string; - userId: string; - }; + let parsedKey: { + type: "guild" | "call"; + channelId: string; + guildId?: string; + userId: string; + }; - try { - parsedKey = parseStreamKey(body.stream_key); - } catch (e) { - return this.close(4000, "Invalid stream key"); - } + try { + parsedKey = parseStreamKey(body.stream_key); + } catch (e) { + return this.close(4000, "Invalid stream key"); + } - const { type, channelId, guildId, userId } = parsedKey; + const { type, channelId, guildId, userId } = parsedKey; - const stream = await Stream.findOne({ - where: { channel_id: channelId, owner_id: userId }, - relations: ["channel"], - }); + const stream = await Stream.findOne({ + where: { channel_id: channelId, owner_id: userId }, + relations: ["channel"], + }); - if (!stream) return this.close(4000, "Invalid stream key"); + if (!stream) return this.close(4000, "Invalid stream key"); - if (type === "guild" && stream.channel.guild_id != guildId) return this.close(4000, "Invalid stream key"); + if (type === "guild" && stream.channel.guild_id != guildId) return this.close(4000, "Invalid stream key"); - const regions = Config.get().regions; - const guildRegion = regions.available.find((r) => r.endpoint === stream.endpoint); + const regions = Config.get().regions; + const guildRegion = regions.available.find((r) => r.endpoint === stream.endpoint); - if (!guildRegion) return this.close(4000, "Unknown region"); + if (!guildRegion) return this.close(4000, "Unknown region"); - const streamSession = StreamSession.create({ - stream_id: stream.id, - user_id: this.user_id, - session_id: this.session_id, - token: genVoiceToken(), - }); + const streamSession = StreamSession.create({ + stream_id: stream.id, + user_id: this.user_id, + session_id: this.session_id, + token: genVoiceToken(), + }); - await streamSession.save(); + await streamSession.save(); - // get the viewers: stream session tokens for this stream that have been used but not including stream owner - const viewers = await StreamSession.find({ - where: { - stream_id: stream.id, - used: true, - user_id: Not(stream.owner_id), - }, - }); + // get the viewers: stream session tokens for this stream that have been used but not including stream owner + const viewers = await StreamSession.find({ + where: { + stream_id: stream.id, + used: true, + user_id: Not(stream.owner_id), + }, + }); - await emitEvent({ - event: "STREAM_CREATE", - data: { - stream_key: body.stream_key, - rtc_server_id: stream.id, // for voice connections in guilds it is guild_id, for dm voice calls it seems to be DM channel id, for GoLive streams a generated number - viewer_ids: viewers.map((v) => v.user_id), - region: guildRegion.name, - paused: false, - }, - channel_id: channelId, - user_id: this.user_id, - } as StreamCreateEvent); + await emitEvent({ + event: "STREAM_CREATE", + data: { + stream_key: body.stream_key, + rtc_server_id: stream.id, // for voice connections in guilds it is guild_id, for dm voice calls it seems to be DM channel id, for GoLive streams a generated number + viewer_ids: viewers.map((v) => v.user_id), + region: guildRegion.name, + paused: false, + }, + channel_id: channelId, + user_id: this.user_id, + } as StreamCreateEvent); - await emitEvent({ - event: "STREAM_SERVER_UPDATE", - data: { - token: streamSession.token, - stream_key: body.stream_key, - guild_id: null, // not sure why its always null - endpoint: stream.endpoint, - }, - user_id: this.user_id, - } as StreamServerUpdateEvent); + await emitEvent({ + event: "STREAM_SERVER_UPDATE", + data: { + token: streamSession.token, + stream_key: body.stream_key, + guild_id: null, // not sure why its always null + endpoint: stream.endpoint, + }, + user_id: this.user_id, + } as StreamServerUpdateEvent); - console.log(`[Gateway] STREAM_WATCH for user ${this.user_id} in channel ${channelId} with stream key ${body.stream_key} in ${Date.now() - startTime}ms`); + console.log(`[Gateway] STREAM_WATCH for user ${this.user_id} in channel ${channelId} with stream key ${body.stream_key} in ${Date.now() - startTime}ms`); } diff --git a/src/gateway/opcodes/VoiceStateUpdate.ts b/src/gateway/opcodes/VoiceStateUpdate.ts index ea7f29e..08a8181 100644 --- a/src/gateway/opcodes/VoiceStateUpdate.ts +++ b/src/gateway/opcodes/VoiceStateUpdate.ts @@ -28,118 +28,118 @@ // Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not. export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { - const startTime = Date.now(); - check.call(this, VoiceStateUpdateSchema, data.d); - const body = data.d as VoiceStateUpdateSchema; - const isNew = body.channel_id === null && body.guild_id === null; - let isChanged = false; + const startTime = Date.now(); + check.call(this, VoiceStateUpdateSchema, data.d); + const body = data.d as VoiceStateUpdateSchema; + const isNew = body.channel_id === null && body.guild_id === null; + let isChanged = false; - let prevState; + let prevState; - let voiceState: VoiceState; - try { - voiceState = await VoiceState.findOneOrFail({ - where: { user_id: this.user_id }, - }); - if (voiceState.session_id !== this.session_id && body.channel_id === null) { - //Should we also check guild_id === null? - //changing deaf or mute on a client that's not the one with the same session of the voicestate in the database should be ignored - return; - } + let voiceState: VoiceState; + try { + voiceState = await VoiceState.findOneOrFail({ + where: { user_id: this.user_id }, + }); + if (voiceState.session_id !== this.session_id && body.channel_id === null) { + //Should we also check guild_id === null? + //changing deaf or mute on a client that's not the one with the same session of the voicestate in the database should be ignored + return; + } - if (voiceState.channel_id !== body.channel_id) isChanged = true; + if (voiceState.channel_id !== body.channel_id) isChanged = true; - //If a user change voice channel between guild we should send a left event first - if (voiceState.guild_id && voiceState.guild_id !== body.guild_id && voiceState.session_id === this.session_id) { - await emitEvent({ - event: "VOICE_STATE_UPDATE", - data: { ...voiceState.toPublicVoiceState(), channel_id: null }, - guild_id: voiceState.guild_id, - }); - } + //If a user change voice channel between guild we should send a left event first + if (voiceState.guild_id && voiceState.guild_id !== body.guild_id && voiceState.session_id === this.session_id) { + await emitEvent({ + event: "VOICE_STATE_UPDATE", + data: { ...voiceState.toPublicVoiceState(), channel_id: null }, + guild_id: voiceState.guild_id, + }); + } - //The event send by Discord's client on channel leave has both guild_id and channel_id as null - //if (body.guild_id === null) body.guild_id = voiceState.guild_id; - prevState = { ...voiceState }; - voiceState.assign(body); - } catch (error) { - voiceState = VoiceState.create({ - ...body, - user_id: this.user_id, - deaf: false, - mute: false, - suppress: false, - }); - } + //The event send by Discord's client on channel leave has both guild_id and channel_id as null + //if (body.guild_id === null) body.guild_id = voiceState.guild_id; + prevState = { ...voiceState }; + voiceState.assign(body); + } catch (error) { + voiceState = VoiceState.create({ + ...body, + user_id: this.user_id, + deaf: false, + mute: false, + suppress: false, + }); + } - // if user left voice channel, send an update to previous channel/guild to let other people know that the user left - if (voiceState.session_id === this.session_id && body.guild_id == null && body.channel_id == null && (prevState?.guild_id || prevState?.channel_id)) { - await emitEvent({ - event: "VOICE_STATE_UPDATE", - data: { - ...voiceState.toPublicVoiceState(), - channel_id: null, - guild_id: null, - }, - guild_id: prevState?.guild_id, - channel_id: prevState?.channel_id, - }); - } + // if user left voice channel, send an update to previous channel/guild to let other people know that the user left + if (voiceState.session_id === this.session_id && body.guild_id == null && body.channel_id == null && (prevState?.guild_id || prevState?.channel_id)) { + await emitEvent({ + event: "VOICE_STATE_UPDATE", + data: { + ...voiceState.toPublicVoiceState(), + channel_id: null, + guild_id: null, + }, + guild_id: prevState?.guild_id, + channel_id: prevState?.channel_id, + }); + } - //TODO the member should only have these properties: hoisted_role, deaf, joined_at, mute, roles, user - //TODO the member.user should only have these properties: avatar, discriminator, id, username - //TODO this may fail - if (body.guild_id) { - voiceState.member = await Member.findOneOrFail({ - where: { id: voiceState.user_id, guild_id: voiceState.guild_id }, - relations: ["user", "roles"], - }); - } + //TODO the member should only have these properties: hoisted_role, deaf, joined_at, mute, roles, user + //TODO the member.user should only have these properties: avatar, discriminator, id, username + //TODO this may fail + if (body.guild_id) { + voiceState.member = await Member.findOneOrFail({ + where: { id: voiceState.user_id, guild_id: voiceState.guild_id }, + relations: ["user", "roles"], + }); + } - //If the session changed we generate a new token - if (voiceState.session_id !== this.session_id) voiceState.token = genVoiceToken(); - voiceState.session_id = this.session_id; + //If the session changed we generate a new token + if (voiceState.session_id !== this.session_id) voiceState.token = genVoiceToken(); + voiceState.session_id = this.session_id; - const { member } = voiceState; + const { member } = voiceState; - await Promise.all([ - voiceState.save(), - emitEvent({ - event: "VOICE_STATE_UPDATE", - data: { - ...voiceState.toPublicVoiceState(), - member: member?.toPublicMember(), - }, - guild_id: voiceState.guild_id, - channel_id: voiceState.channel_id, - user_id: voiceState.user_id, - } as VoiceStateUpdateEvent), - ]); + await Promise.all([ + voiceState.save(), + emitEvent({ + event: "VOICE_STATE_UPDATE", + data: { + ...voiceState.toPublicVoiceState(), + member: member?.toPublicMember(), + }, + guild_id: voiceState.guild_id, + channel_id: voiceState.channel_id, + user_id: voiceState.user_id, + } as VoiceStateUpdateEvent), + ]); - //If it's null it means that we are leaving the channel and this event is not needed - if ((isNew || isChanged) && voiceState.channel_id !== null) { - const guild = await Guild.findOne({ - where: { id: voiceState.guild_id }, - }); - const regions = Config.get().regions; - let guildRegion: Region; - if (guild && guild.region) { - guildRegion = regions.available.filter((r) => r.id === guild.region)[0]; - } else { - guildRegion = regions.available.filter((r) => r.id === regions.default)[0]; - } + //If it's null it means that we are leaving the channel and this event is not needed + if ((isNew || isChanged) && voiceState.channel_id !== null) { + const guild = await Guild.findOne({ + where: { id: voiceState.guild_id }, + }); + const regions = Config.get().regions; + let guildRegion: Region; + if (guild && guild.region) { + guildRegion = regions.available.filter((r) => r.id === guild.region)[0]; + } else { + guildRegion = regions.available.filter((r) => r.id === regions.default)[0]; + } - await emitEvent({ - event: "VOICE_SERVER_UPDATE", - data: { - token: voiceState.token, - guild_id: voiceState.guild_id, - endpoint: guildRegion.endpoint, - channel_id: voiceState.guild_id ? undefined : voiceState.channel_id, // only DM voice calls have this set, and DM channel is one where guild_id is null - }, - user_id: voiceState.user_id, - } as VoiceServerUpdateEvent); - } + await emitEvent({ + event: "VOICE_SERVER_UPDATE", + data: { + token: voiceState.token, + guild_id: voiceState.guild_id, + endpoint: guildRegion.endpoint, + channel_id: voiceState.guild_id ? undefined : voiceState.channel_id, // only DM voice calls have this set, and DM channel is one where guild_id is null + }, + user_id: voiceState.user_id, + } as VoiceServerUpdateEvent); + } - console.log(`[Gateway] VOICE_STATE_UPDATE for user ${this.user_id} in channel ${voiceState.channel_id} in guild ${voiceState.guild_id} in ${Date.now() - startTime}ms`); + console.log(`[Gateway] VOICE_STATE_UPDATE for user ${this.user_id} in channel ${voiceState.channel_id} in guild ${voiceState.guild_id} in ${Date.now() - startTime}ms`); } diff --git a/src/gateway/opcodes/experiments.json b/src/gateway/opcodes/experiments.json index 0370b5d..be6985b 100644 --- a/src/gateway/opcodes/experiments.json +++ b/src/gateway/opcodes/experiments.json @@ -1,76 +1,76 @@ [ - [4047587481, 0, 0, -1, 0], - [1509401575, 0, 1, -1, 0], - [1865079242, 0, 1, -1, 0], - [1962538549, 1, 0, -1, 0], - [3816091942, 3, 2, -1, 0], - [4130837190, 0, 10, -1, 0], - [1861568052, 0, 1, -1, 0], - [2290910058, 6, 2, -1, 0], - [1578940118, 1, 1, -1, 0], - [1571676964, 0, 1, -1, 2], - [3640172371, 0, 2, -1, 2], - [1658164312, 2, 1, -1, 0], - [98883956, 1, 1, -1, 0], - [3114091169, 0, 1, -1, 0], - [2570684145, 4, 1, -1, 2], - [4007615411, 0, 1, -1, 0], - [3665310159, 2, 1, -1, 1], - [852550504, 3, 1, -1, 0], - [2333572067, 0, 1, -1, 0], - [935994771, 1, 1, -1, 0], - [1127795596, 1, 1, -1, 0], - [4168223991, 0, 1, -1, 0], - [18585280, 0, 1, -1, 1], - [327482016, 0, 1, -1, 2], - [3458098201, 7, 1, -1, 0], - [478613943, 2, 1, -1, 1], - [2792197902, 0, 1, -1, 2], - [284670956, 0, 1, -1, 0], - [2099185390, 0, 1, -1, 0], - [1202202685, 0, 1, -1, 0], - [2122174751, 0, 1, -1, 0], - [3633864632, 0, 1, -1, 0], - [3103053065, 0, 1, -1, 0], - [820624960, 0, 1, -1, 0], - [1134479292, 0, 1, -1, 0], - [2511257455, 3, 1, -1, 3], - [2599708267, 0, 1, -1, 0], - [613180822, 1, 1, -1, 0], - [2885186814, 0, 1, -1, 0], - [221503477, 0, 1, -1, 0], - [1054317075, 0, 1, -1, 3], - [683872522, 0, 1, -1, 1], - [1739278764, 0, 2, -1, 0], - [2855249023, 0, 1, -1, 0], - [3721841948, 0, 1, -1, 0], - [1285203515, 0, 1, -1, 0], - [1365487849, 6, 1, -1, 0], - [955229746, 0, 1, -1, 0], - [3128009767, 0, 10, -1, 0], - [441885003, 0, 1, -1, 0], - [3433971238, 0, 1, -1, 2], - [1038765354, 3, 1, -1, 0], - [1174347196, 0, 1, -1, 0], - [3649806352, 1, 1, -1, 0], - [2973729510, 2, 1, -1, 0], - [2571931329, 1, 6, -1, 0], - [3884442008, 0, 1, -1, 0], - [978673395, 1, 1, -1, 0], - [4050927174, 0, 1, -1, 0], - [1260103069, 0, 1, -1, 0], - [4168894280, 0, 1, -1, 0], - [4045587091, 0, 1, -1, 0], - [2003494159, 1, 1, -1, 0], - [51193042, 0, 1, -1, 0], - [2634540382, 3, 1, -1, 0], - [886364171, 0, 1, -1, 0], - [3898604944, 0, 1, -1, 0], - [3388129398, 0, 1, -1, 0], - [3964382884, 2, 1, -1, 1], - [3305874255, 0, 1, -1, 0], - [156590431, 0, 1, -1, 0], - [3106485751, 0, 0, -1, 0], - [3035674767, 0, 1, -1, 0], - [851697110, 0, 1, -1, 0] + [4047587481, 0, 0, -1, 0], + [1509401575, 0, 1, -1, 0], + [1865079242, 0, 1, -1, 0], + [1962538549, 1, 0, -1, 0], + [3816091942, 3, 2, -1, 0], + [4130837190, 0, 10, -1, 0], + [1861568052, 0, 1, -1, 0], + [2290910058, 6, 2, -1, 0], + [1578940118, 1, 1, -1, 0], + [1571676964, 0, 1, -1, 2], + [3640172371, 0, 2, -1, 2], + [1658164312, 2, 1, -1, 0], + [98883956, 1, 1, -1, 0], + [3114091169, 0, 1, -1, 0], + [2570684145, 4, 1, -1, 2], + [4007615411, 0, 1, -1, 0], + [3665310159, 2, 1, -1, 1], + [852550504, 3, 1, -1, 0], + [2333572067, 0, 1, -1, 0], + [935994771, 1, 1, -1, 0], + [1127795596, 1, 1, -1, 0], + [4168223991, 0, 1, -1, 0], + [18585280, 0, 1, -1, 1], + [327482016, 0, 1, -1, 2], + [3458098201, 7, 1, -1, 0], + [478613943, 2, 1, -1, 1], + [2792197902, 0, 1, -1, 2], + [284670956, 0, 1, -1, 0], + [2099185390, 0, 1, -1, 0], + [1202202685, 0, 1, -1, 0], + [2122174751, 0, 1, -1, 0], + [3633864632, 0, 1, -1, 0], + [3103053065, 0, 1, -1, 0], + [820624960, 0, 1, -1, 0], + [1134479292, 0, 1, -1, 0], + [2511257455, 3, 1, -1, 3], + [2599708267, 0, 1, -1, 0], + [613180822, 1, 1, -1, 0], + [2885186814, 0, 1, -1, 0], + [221503477, 0, 1, -1, 0], + [1054317075, 0, 1, -1, 3], + [683872522, 0, 1, -1, 1], + [1739278764, 0, 2, -1, 0], + [2855249023, 0, 1, -1, 0], + [3721841948, 0, 1, -1, 0], + [1285203515, 0, 1, -1, 0], + [1365487849, 6, 1, -1, 0], + [955229746, 0, 1, -1, 0], + [3128009767, 0, 10, -1, 0], + [441885003, 0, 1, -1, 0], + [3433971238, 0, 1, -1, 2], + [1038765354, 3, 1, -1, 0], + [1174347196, 0, 1, -1, 0], + [3649806352, 1, 1, -1, 0], + [2973729510, 2, 1, -1, 0], + [2571931329, 1, 6, -1, 0], + [3884442008, 0, 1, -1, 0], + [978673395, 1, 1, -1, 0], + [4050927174, 0, 1, -1, 0], + [1260103069, 0, 1, -1, 0], + [4168894280, 0, 1, -1, 0], + [4045587091, 0, 1, -1, 0], + [2003494159, 1, 1, -1, 0], + [51193042, 0, 1, -1, 0], + [2634540382, 3, 1, -1, 0], + [886364171, 0, 1, -1, 0], + [3898604944, 0, 1, -1, 0], + [3388129398, 0, 1, -1, 0], + [3964382884, 2, 1, -1, 1], + [3305874255, 0, 1, -1, 0], + [156590431, 0, 1, -1, 0], + [3106485751, 0, 0, -1, 0], + [3035674767, 0, 1, -1, 0], + [851697110, 0, 1, -1, 0] ] diff --git a/src/gateway/opcodes/index.ts b/src/gateway/opcodes/index.ts index 7e6416c..358a942 100644 --- a/src/gateway/opcodes/index.ts +++ b/src/gateway/opcodes/index.ts @@ -34,24 +34,24 @@ export type OPCodeHandler = (this: WebSocket, data: Payload) => unknown; export default { - 1: onHeartbeat, - 2: onIdentify, - 3: onPresenceUpdate, - 4: onVoiceStateUpdate, - // 5: Voice Server Ping - 6: onResume, - // 7: Reconnect: You should attempt to reconnect and resume immediately. - 8: onRequestGuildMembers, - // 9: Invalid Session - // 10: Hello - 12: onGuildSync, // technically deprecated, bt should be less finnicky? - // 13: Dm_update - 14: onLazyRequest, - 18: onStreamCreate, - 19: onStreamDelete, - 20: onStreamWatch, - 36: onRequestChannelStatuses, - 37: onGuildSubscriptionsBulk, - 40: onHeartbeat, // same as 1, except with extra data - 41: () => {}, // "Update Time Spent Session ID", just tracking nonsense + 1: onHeartbeat, + 2: onIdentify, + 3: onPresenceUpdate, + 4: onVoiceStateUpdate, + // 5: Voice Server Ping + 6: onResume, + // 7: Reconnect: You should attempt to reconnect and resume immediately. + 8: onRequestGuildMembers, + // 9: Invalid Session + // 10: Hello + 12: onGuildSync, // technically deprecated, bt should be less finnicky? + // 13: Dm_update + 14: onLazyRequest, + 18: onStreamCreate, + 19: onStreamDelete, + 20: onStreamWatch, + 36: onRequestChannelStatuses, + 37: onGuildSubscriptionsBulk, + 40: onHeartbeat, // same as 1, except with extra data + 41: () => {}, // "Update Time Spent Session ID", just tracking nonsense } as { [key: number]: OPCodeHandler }; diff --git a/src/gateway/opcodes/instanceOf.ts b/src/gateway/opcodes/instanceOf.ts index 39bece1..3c85180 100644 --- a/src/gateway/opcodes/instanceOf.ts +++ b/src/gateway/opcodes/instanceOf.ts @@ -21,16 +21,16 @@ import { CLOSECODES } from "../util/Constants"; export function check(this: WebSocket, schema: unknown, data: unknown) { - try { - const error = instanceOf(schema, data, { path: "body" }); - if (error !== true) { - throw error; - } - return true; - } catch (error) { - console.error(error); - // invalid payload - this.close(CLOSECODES.Decode_error); - throw error; - } + try { + const error = instanceOf(schema, data, { path: "body" }); + if (error !== true) { + throw error; + } + return true; + } catch (error) { + console.error(error); + // invalid payload + this.close(CLOSECODES.Decode_error); + throw error; + } } diff --git a/src/gateway/start.ts b/src/gateway/start.ts index 8df38ed..10dfac9 100644 --- a/src/gateway/start.ts +++ b/src/gateway/start.ts @@ -29,6 +29,6 @@ if (isNaN(port)) port = 3002; const server = new Server({ - port, + port, }); server.start(); diff --git a/src/gateway/util/Capabilities.ts b/src/gateway/util/Capabilities.ts index 6c94bb4..b358f29 100644 --- a/src/gateway/util/Capabilities.ts +++ b/src/gateway/util/Capabilities.ts @@ -4,23 +4,23 @@ type CapabilityString = keyof typeof Capabilities.FLAGS; export class Capabilities extends BitField { - static FLAGS = { - // Thanks, Opencord! - // https://github.com/MateriiApps/OpenCord/blob/master/app/src/main/java/com/xinto/opencord/gateway/io/Capabilities.kt - LAZY_USER_NOTES: BitFlag(0), - NO_AFFINE_USER_IDS: BitFlag(1), - VERSIONED_READ_STATES: BitFlag(2), - VERSIONED_USER_GUILD_SETTINGS: BitFlag(3), - DEDUPLICATE_USER_OBJECTS: BitFlag(4), - PRIORITIZED_READY_PAYLOAD: BitFlag(5), - MULTIPLE_GUILD_EXPERIMENT_POPULATIONS: BitFlag(6), - NON_CHANNEL_READ_STATES: BitFlag(7), - AUTH_TOKEN_REFRESH: BitFlag(8), - USER_SETTINGS_PROTO: BitFlag(9), - CLIENT_STATE_V2: BitFlag(10), - PASSIVE_GUILD_UPDATE: BitFlag(11), - }; + static FLAGS = { + // Thanks, Opencord! + // https://github.com/MateriiApps/OpenCord/blob/master/app/src/main/java/com/xinto/opencord/gateway/io/Capabilities.kt + LAZY_USER_NOTES: BitFlag(0), + NO_AFFINE_USER_IDS: BitFlag(1), + VERSIONED_READ_STATES: BitFlag(2), + VERSIONED_USER_GUILD_SETTINGS: BitFlag(3), + DEDUPLICATE_USER_OBJECTS: BitFlag(4), + PRIORITIZED_READY_PAYLOAD: BitFlag(5), + MULTIPLE_GUILD_EXPERIMENT_POPULATIONS: BitFlag(6), + NON_CHANNEL_READ_STATES: BitFlag(7), + AUTH_TOKEN_REFRESH: BitFlag(8), + USER_SETTINGS_PROTO: BitFlag(9), + CLIENT_STATE_V2: BitFlag(10), + PASSIVE_GUILD_UPDATE: BitFlag(11), + }; - any = (capability: CapabilityResolvable) => super.any(capability); - has = (capability: CapabilityResolvable) => super.has(capability); + any = (capability: CapabilityResolvable) => super.any(capability); + has = (capability: CapabilityResolvable) => super.has(capability); } diff --git a/src/gateway/util/Constants.ts b/src/gateway/util/Constants.ts index a16bea7..af5fa1a 100644 --- a/src/gateway/util/Constants.ts +++ b/src/gateway/util/Constants.ts @@ -17,58 +17,58 @@ */ export enum OPCODES { - Dispatch = 0, - Heartbeat = 1, - Identify = 2, - Presence_Update = 3, - Voice_State_Update = 4, - Voice_Server_Ping = 5, // ? What is opcode 5? - Resume = 6, - Reconnect = 7, - Request_Guild_Members = 8, - Invalid_Session = 9, - Hello = 10, - Heartbeat_ACK = 11, - Guild_Sync = 12, - DM_Update = 13, - Lazy_Request = 14, - Lobby_Connect = 15, - Lobby_Disconnect = 16, - Lobby_Voice_States_Update = 17, - Stream_Create = 18, - Stream_Delete = 19, - Stream_Watch = 20, - Stream_Ping = 21, - Stream_Set_Paused = 22, - Request_Application_Commands = 24, - // We don't know the names for these: - ThirtySix = 36, // this is just a guild id? - Guild_Subscriptions_Bulk = 37, // Already implemented it seems? - SetQoS = 40, - ClientInitSession = 41, + Dispatch = 0, + Heartbeat = 1, + Identify = 2, + Presence_Update = 3, + Voice_State_Update = 4, + Voice_Server_Ping = 5, // ? What is opcode 5? + Resume = 6, + Reconnect = 7, + Request_Guild_Members = 8, + Invalid_Session = 9, + Hello = 10, + Heartbeat_ACK = 11, + Guild_Sync = 12, + DM_Update = 13, + Lazy_Request = 14, + Lobby_Connect = 15, + Lobby_Disconnect = 16, + Lobby_Voice_States_Update = 17, + Stream_Create = 18, + Stream_Delete = 19, + Stream_Watch = 20, + Stream_Ping = 21, + Stream_Set_Paused = 22, + Request_Application_Commands = 24, + // We don't know the names for these: + ThirtySix = 36, // this is just a guild id? + Guild_Subscriptions_Bulk = 37, // Already implemented it seems? + SetQoS = 40, + ClientInitSession = 41, } export enum CLOSECODES { - Unknown_error = 4000, - Unknown_opcode, - Decode_error, - Not_authenticated, - Authentication_failed, - Already_authenticated, - Invalid_session, - Invalid_seq, - Rate_limited, - Session_timed_out, - Invalid_shard, - Sharding_required, - Invalid_API_version, - Invalid_intent, - Disallowed_intent, + Unknown_error = 4000, + Unknown_opcode, + Decode_error, + Not_authenticated, + Authentication_failed, + Already_authenticated, + Invalid_session, + Invalid_seq, + Rate_limited, + Session_timed_out, + Invalid_shard, + Sharding_required, + Invalid_API_version, + Invalid_intent, + Disallowed_intent, } export interface Payload { - op: OPCODES; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - d?: any; - s?: number; - t?: string; + op: OPCODES; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + d?: any; + s?: number; + t?: string; } diff --git a/src/gateway/util/Heartbeat.ts b/src/gateway/util/Heartbeat.ts index f39c487..9874ba9 100644 --- a/src/gateway/util/Heartbeat.ts +++ b/src/gateway/util/Heartbeat.ts @@ -21,9 +21,9 @@ // TODO: make heartbeat timeout configurable export function setHeartbeat(socket: WebSocket) { - if (socket.heartbeatTimeout) clearTimeout(socket.heartbeatTimeout); + if (socket.heartbeatTimeout) clearTimeout(socket.heartbeatTimeout); - socket.heartbeatTimeout = setTimeout(() => { - return socket.close(CLOSECODES.Session_timed_out); - }, 1000 * 45); + socket.heartbeatTimeout = setTimeout(() => { + return socket.close(CLOSECODES.Session_timed_out); + }, 1000 * 45); } diff --git a/src/gateway/util/Send.ts b/src/gateway/util/Send.ts index 90c1f94..95902ca 100644 --- a/src/gateway/util/Send.ts +++ b/src/gateway/util/Send.ts @@ -23,66 +23,66 @@ import { ErlpackType, JSONReplacer } from "@spacebar/util"; let erlpack: ErlpackType | null = null; try { - erlpack = require("@yukikaze-bot/erlpack") as ErlpackType; + erlpack = require("@yukikaze-bot/erlpack") as ErlpackType; } catch (e) { - console.log("Failed to import @yukikaze-bot/erlpack: ", e); + console.log("Failed to import @yukikaze-bot/erlpack: ", e); } // don't care // eslint-disable-next-line @typescript-eslint/no-explicit-any const recurseJsonReplace = (json: any) => { - for (const key in json) { - // eslint-disable-next-line no-prototype-builtins - if (!json.hasOwnProperty(key)) continue; + for (const key in json) { + // eslint-disable-next-line no-prototype-builtins + if (!json.hasOwnProperty(key)) continue; - json[key] = JSONReplacer.call(json, key, json[key]); + json[key] = JSONReplacer.call(json, key, json[key]); - if (typeof json[key] == "object" && json[key] !== null) json[key] = recurseJsonReplace(json[key]); - } - return json; + if (typeof json[key] == "object" && json[key] !== null) json[key] = recurseJsonReplace(json[key]); + } + return json; }; export async function Send(socket: WebSocket, data: Payload) { - if (process.env.WS_VERBOSE) console.log(`[Websocket] Outgoing message: ${JSON.stringify(data)}`); + if (process.env.WS_VERBOSE) console.log(`[Websocket] Outgoing message: ${JSON.stringify(data)}`); - if (process.env.WS_DUMP) { - const id = socket.session_id || "unknown"; + if (process.env.WS_DUMP) { + const id = socket.session_id || "unknown"; - await fs.mkdir(path.join("dump", id), { - recursive: true, - }); - await fs.writeFile(path.join("dump", id, `${Date.now()}.out.json`), JSON.stringify(data, null, 2)); - } + await fs.mkdir(path.join("dump", id), { + recursive: true, + }); + await fs.writeFile(path.join("dump", id, `${Date.now()}.out.json`), JSON.stringify(data, null, 2)); + } - let buffer: Buffer | string; - if (socket.encoding === "etf" && erlpack) { - // Erlpack doesn't like Date objects, encodes them as {} - data = recurseJsonReplace(data); - buffer = erlpack.pack(data); - } - // TODO: encode circular object - else if (socket.encoding === "json") buffer = JSON.stringify(data, JSONReplacer); - else return; + let buffer: Buffer | string; + if (socket.encoding === "etf" && erlpack) { + // Erlpack doesn't like Date objects, encodes them as {} + data = recurseJsonReplace(data); + buffer = erlpack.pack(data); + } + // TODO: encode circular object + else if (socket.encoding === "json") buffer = JSON.stringify(data, JSONReplacer); + else return; - // TODO: compression - if (socket.compress === "zlib-stream") { - buffer = socket.deflate!.process(buffer) as Buffer; - } else if (socket.compress === "zstd-stream") { - if (typeof buffer === "string") buffer = Buffer.from(buffer as string); + // TODO: compression + if (socket.compress === "zlib-stream") { + buffer = socket.deflate!.process(buffer) as Buffer; + } else if (socket.compress === "zstd-stream") { + if (typeof buffer === "string") buffer = Buffer.from(buffer as string); - buffer = (await socket.zstdEncoder!.encode(buffer as Buffer)) as Buffer; - } + buffer = (await socket.zstdEncoder!.encode(buffer as Buffer)) as Buffer; + } - return new Promise((res, rej) => { - if (socket.readyState !== 1) { - // return rej("socket not open"); - socket.close(); - return; - } + return new Promise((res, rej) => { + if (socket.readyState !== 1) { + // return rej("socket not open"); + socket.close(); + return; + } - socket.send(buffer, (err) => { - if (err) return rej(err); - return res(null); - }); - }); + socket.send(buffer, (err) => { + if (err) return rej(err); + return res(null); + }); + }); } diff --git a/src/gateway/util/SessionUtils.ts b/src/gateway/util/SessionUtils.ts index 26e7bdd..843341c 100644 --- a/src/gateway/util/SessionUtils.ts +++ b/src/gateway/util/SessionUtils.ts @@ -17,13 +17,13 @@ */ export function genSessionId() { - return genRanHex(32); + return genRanHex(32); } export function genVoiceToken() { - return genRanHex(16); + return genRanHex(16); } function genRanHex(size: number) { - return [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""); + return [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""); } diff --git a/src/gateway/util/Utils.ts b/src/gateway/util/Utils.ts index 614b074..f60bfbf 100644 --- a/src/gateway/util/Utils.ts +++ b/src/gateway/util/Utils.ts @@ -1,38 +1,38 @@ import { VoiceState } from "@spacebar/util"; export function parseStreamKey(streamKey: string): { - type: "guild" | "call"; - channelId: string; - guildId?: string; - userId: string; + type: "guild" | "call"; + channelId: string; + guildId?: string; + userId: string; } { - const streamKeyArray = streamKey.split(":"); + const streamKeyArray = streamKey.split(":"); - const type = streamKeyArray.shift(); + const type = streamKeyArray.shift(); - if (type !== "guild" && type !== "call") { - throw new Error(`Invalid stream key type: ${type}`); - } + if (type !== "guild" && type !== "call") { + throw new Error(`Invalid stream key type: ${type}`); + } - if ((type === "guild" && streamKeyArray.length < 3) || (type === "call" && streamKeyArray.length < 2)) throw new Error(`Invalid stream key: ${streamKey}`); // invalid stream key + if ((type === "guild" && streamKeyArray.length < 3) || (type === "call" && streamKeyArray.length < 2)) throw new Error(`Invalid stream key: ${streamKey}`); // invalid stream key - let guildId: string | undefined; - if (type === "guild") { - guildId = streamKeyArray.shift(); - } - const channelId = streamKeyArray.shift(); - const userId = streamKeyArray.shift(); + let guildId: string | undefined; + if (type === "guild") { + guildId = streamKeyArray.shift(); + } + const channelId = streamKeyArray.shift(); + const userId = streamKeyArray.shift(); - if (!channelId || !userId) { - throw new Error(`Invalid stream key: ${streamKey}`); - } - return { type, channelId, guildId, userId }; + if (!channelId || !userId) { + throw new Error(`Invalid stream key: ${streamKey}`); + } + return { type, channelId, guildId, userId }; } export function generateStreamKey(type: "guild" | "call", guildId: string | undefined, channelId: string, userId: string): string { - const streamKey = `${type}${type === "guild" ? `:${guildId}` : ""}:${channelId}:${userId}`; + const streamKey = `${type}${type === "guild" ? `:${guildId}` : ""}:${channelId}:${userId}`; - return streamKey; + return streamKey; } // Temporary cleanup function until shutdown cleanup function is fixed. @@ -40,18 +40,18 @@ // TODO: remove this when Server.stop() is fixed so that it waits for all websocket connections to run their // respective Close event listener function for session cleanup export async function cleanupOnStartup(): Promise { - // TODO: how is this different from clearing the table? - //await VoiceState.update( - // {}, - // { - // // @ts-expect-error channel_id is nullable - // channel_id: null, - // // @ts-expect-error guild_id is nullable - // guild_id: null, - // self_stream: false, - // self_video: false, - // }, - //); + // TODO: how is this different from clearing the table? + //await VoiceState.update( + // {}, + // { + // // @ts-expect-error channel_id is nullable + // channel_id: null, + // // @ts-expect-error guild_id is nullable + // guild_id: null, + // self_stream: false, + // self_video: false, + // }, + //); - await VoiceState.clear(); + await VoiceState.clear(); } diff --git a/src/gateway/util/WebSocket.ts b/src/gateway/util/WebSocket.ts index cbf8a73..af08542 100644 --- a/src/gateway/util/WebSocket.ts +++ b/src/gateway/util/WebSocket.ts @@ -24,30 +24,30 @@ import { QoSPayload } from "../opcodes/Heartbeat"; export interface WebSocket extends WS { - version: number; - user_id: string; - session_id: string; - encoding: "etf" | "json"; - compress?: "zlib-stream" | "zstd-stream"; - ipAddress?: string; - userAgent?: string; // for cdn request signing - fingerprint?: string; - shard_count?: bigint; - shard_id?: bigint; - deflate?: Deflate; - inflate?: Inflate; - zstdEncoder?: Encoder; - zstdDecoder?: Decoder; - heartbeatTimeout: NodeJS.Timeout; - readyTimeout: NodeJS.Timeout; - intents: Intents; - sequence: number; - permissions: Record; - events: Record Promise)>; - member_events: Record Promise>; - listen_options: ListenEventOpts; - capabilities?: Capabilities; - large_threshold: number; - qos?: QoSPayload; - session?: Session; + version: number; + user_id: string; + session_id: string; + encoding: "etf" | "json"; + compress?: "zlib-stream" | "zstd-stream"; + ipAddress?: string; + userAgent?: string; // for cdn request signing + fingerprint?: string; + shard_count?: bigint; + shard_id?: bigint; + deflate?: Deflate; + inflate?: Inflate; + zstdEncoder?: Encoder; + zstdDecoder?: Decoder; + heartbeatTimeout: NodeJS.Timeout; + readyTimeout: NodeJS.Timeout; + intents: Intents; + sequence: number; + permissions: Record; + events: Record Promise)>; + member_events: Record Promise>; + listen_options: ListenEventOpts; + capabilities?: Capabilities; + large_threshold: number; + qos?: QoSPayload; + session?: Session; } diff --git a/src/schemas/CreateFingerprintResponse.ts b/src/schemas/CreateFingerprintResponse.ts index 3182ad9..9e60c21 100644 --- a/src/schemas/CreateFingerprintResponse.ts +++ b/src/schemas/CreateFingerprintResponse.ts @@ -1,3 +1,3 @@ export interface CreateFingerprintResponse { - fingerprint: string; + fingerprint: string; } diff --git a/src/schemas/HelperTypes.ts b/src/schemas/HelperTypes.ts index 252d0a6..e1e04f6 100644 --- a/src/schemas/HelperTypes.ts +++ b/src/schemas/HelperTypes.ts @@ -17,5 +17,5 @@ */ export type StringStringDictionary = { - [key: string]: string; + [key: string]: string; }; diff --git a/src/schemas/Validator.ts b/src/schemas/Validator.ts index e75f37d..e7a227a 100644 --- a/src/schemas/Validator.ts +++ b/src/schemas/Validator.ts @@ -40,24 +40,24 @@ // } export const ajv = new Ajv({ - allErrors: true, - parseDate: true, - allowDate: true, - schemas: schemas, - coerceTypes: true, - messages: true, - strict: true, - strictRequired: true, - allowUnionTypes: true, + allErrors: true, + parseDate: true, + allowDate: true, + schemas: schemas, + coerceTypes: true, + messages: true, + strict: true, + strictRequired: true, + allowUnionTypes: true, }); addFormats(ajv); export function validateSchema(schema: string, data: G): G { - const valid = ajv.validate(schema, data); - if (!valid) { - console.log("[Validator] Validation error in ", schema); - throw ajv.errors; - } - return data; + const valid = ajv.validate(schema, data); + if (!valid) { + console.log("[Validator] Validation error in ", schema); + throw ajv.errors; + } + return data; } diff --git a/src/schemas/api/bots/ApplicationCommandCreateSchema.ts b/src/schemas/api/bots/ApplicationCommandCreateSchema.ts index 3bb39d5..4408a9e 100644 --- a/src/schemas/api/bots/ApplicationCommandCreateSchema.ts +++ b/src/schemas/api/bots/ApplicationCommandCreateSchema.ts @@ -1,28 +1,28 @@ import { - ApplicationCommandHandlerType, - ApplicationCommandOption, - ApplicationCommandType, - ApplicationIntegrationType, - InteractionContextType, - StringStringDictionary, + ApplicationCommandHandlerType, + ApplicationCommandOption, + ApplicationCommandType, + ApplicationIntegrationType, + InteractionContextType, + StringStringDictionary, } from "@spacebar/schemas"; export interface ApplicationCommandCreateSchema { - type?: ApplicationCommandType; - name: string; - name_localizations?: StringStringDictionary; - description?: string; - description_localizations?: StringStringDictionary; - options?: ApplicationCommandOption[]; - default_member_permissions?: string; - /* - * @deprecated - */ - dm_permission?: boolean; - nsfw?: boolean; - integration_types?: ApplicationIntegrationType[]; - contexts?: InteractionContextType[]; - handler?: ApplicationCommandHandlerType; + type?: ApplicationCommandType; + name: string; + name_localizations?: StringStringDictionary; + description?: string; + description_localizations?: StringStringDictionary; + options?: ApplicationCommandOption[]; + default_member_permissions?: string; + /* + * @deprecated + */ + dm_permission?: boolean; + nsfw?: boolean; + integration_types?: ApplicationIntegrationType[]; + contexts?: InteractionContextType[]; + handler?: ApplicationCommandHandlerType; } export type BulkApplicationCommandCreateSchema = ApplicationCommandCreateSchema[]; diff --git a/src/schemas/api/bots/ApplicationCommandSchema.ts b/src/schemas/api/bots/ApplicationCommandSchema.ts index c9d3fa9..7d739e4 100644 --- a/src/schemas/api/bots/ApplicationCommandSchema.ts +++ b/src/schemas/api/bots/ApplicationCommandSchema.ts @@ -1,57 +1,57 @@ import { ApplicationCommandOption, Snowflake, StringStringDictionary } from "@spacebar/schemas"; export interface ApplicationCommandSchema { - id?: Snowflake; - type?: ApplicationCommandType; - application_id: Snowflake; - guild_id?: Snowflake; - name: string; - name_localizations?: StringStringDictionary; - name_localized?: string | null; - description: string; - description_localizations?: StringStringDictionary; - description_localized?: string | null; - options?: ApplicationCommandOption[]; - default_member_permissions: string | null; - /* - * @deprecated - */ - dm_permission?: boolean; - permissions?: ApplicationCommandIndexPermissions; - nsfw?: boolean; - integration_types?: ApplicationIntegrationType[]; - global_popularity_rank?: number; - contexts?: InteractionContextType[]; - version: Snowflake; - handler?: ApplicationCommandHandlerType; + id?: Snowflake; + type?: ApplicationCommandType; + application_id: Snowflake; + guild_id?: Snowflake; + name: string; + name_localizations?: StringStringDictionary; + name_localized?: string | null; + description: string; + description_localizations?: StringStringDictionary; + description_localized?: string | null; + options?: ApplicationCommandOption[]; + default_member_permissions: string | null; + /* + * @deprecated + */ + dm_permission?: boolean; + permissions?: ApplicationCommandIndexPermissions; + nsfw?: boolean; + integration_types?: ApplicationIntegrationType[]; + global_popularity_rank?: number; + contexts?: InteractionContextType[]; + version: Snowflake; + handler?: ApplicationCommandHandlerType; } export interface ApplicationCommandIndexPermissions { - user?: boolean; - roles?: Record; - channels?: Record; + user?: boolean; + roles?: Record; + channels?: Record; } export enum ApplicationCommandType { - CHAT_INPUT = 1, - USER = 2, - MESSAGE = 3, - PRIMARY_ENTRY_POINT = 4, + CHAT_INPUT = 1, + USER = 2, + MESSAGE = 3, + PRIMARY_ENTRY_POINT = 4, } export enum ApplicationCommandHandlerType { - APP_HANDLER = 1, - DISCORD_LAUNCH_ACTIVITY = 2, - APP_HANDLER_LAUNCH_ACTIVITY = 3, + APP_HANDLER = 1, + DISCORD_LAUNCH_ACTIVITY = 2, + APP_HANDLER_LAUNCH_ACTIVITY = 3, } export enum InteractionContextType { - GUILD = 0, - BOT_DM = 1, - PRIVATE_CHANNEL = 2, + GUILD = 0, + BOT_DM = 1, + PRIVATE_CHANNEL = 2, } export enum ApplicationIntegrationType { - GUILD_INSTALL = 0, - USER_INSTALL = 1, + GUILD_INSTALL = 0, + USER_INSTALL = 1, } diff --git a/src/schemas/api/bots/InteractionCallbackSchema.ts b/src/schemas/api/bots/InteractionCallbackSchema.ts index 9bc2721..5eb3f29 100644 --- a/src/schemas/api/bots/InteractionCallbackSchema.ts +++ b/src/schemas/api/bots/InteractionCallbackSchema.ts @@ -2,6 +2,6 @@ import { InteractionCallbackType } from "./InteractionCallbackType"; export interface InteractionCallbackSchema { - type: InteractionCallbackType; - data: Message; + type: InteractionCallbackType; + data: Message; } diff --git a/src/schemas/api/bots/InteractionCallbackType.ts b/src/schemas/api/bots/InteractionCallbackType.ts index 2a90b3c..fe9251f 100644 --- a/src/schemas/api/bots/InteractionCallbackType.ts +++ b/src/schemas/api/bots/InteractionCallbackType.ts @@ -1,14 +1,14 @@ export enum InteractionCallbackType { - PONG = 1, - ACKNOWLEDGE = 2, // Deprecated - CHANNEL_MESSAGE = 3, // Deprecated - CHANNEL_MESSAGE_WITH_SOURCE = 4, - DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE = 5, - DEFERRED_UPDATE_MESSAGE = 6, - UPDATE_MESSAGE = 7, - APPLICATION_COMMAND_AUTOCOMPLETE_RESULT = 8, - MODAL = 9, - PREMIUM_REQUIRED = 10, // Deprecated - IFRAME_MODAL = 11, - LAUNCH_ACTIVITY = 12, + PONG = 1, + ACKNOWLEDGE = 2, // Deprecated + CHANNEL_MESSAGE = 3, // Deprecated + CHANNEL_MESSAGE_WITH_SOURCE = 4, + DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE = 5, + DEFERRED_UPDATE_MESSAGE = 6, + UPDATE_MESSAGE = 7, + APPLICATION_COMMAND_AUTOCOMPLETE_RESULT = 8, + MODAL = 9, + PREMIUM_REQUIRED = 10, // Deprecated + IFRAME_MODAL = 11, + LAUNCH_ACTIVITY = 12, } diff --git a/src/schemas/api/bots/InteractionCreateSchema.ts b/src/schemas/api/bots/InteractionCreateSchema.ts index 9c0a628..2f01c59 100644 --- a/src/schemas/api/bots/InteractionCreateSchema.ts +++ b/src/schemas/api/bots/InteractionCreateSchema.ts @@ -2,31 +2,31 @@ import { Channel, InteractionType, Message } from "@spacebar/util"; export interface InteractionCreateSchema { - version: number; // TODO: types? - id: Snowflake; - application_id: Snowflake; - type: InteractionType; - token: string; - data?: object; // TODO: types? - guild?: InteractionGuild; - guild_id?: Snowflake; - guild_locale?: string; - channel?: Channel; - channel_id?: Snowflake; - member?: PublicMember; - user?: PublicUser; - locale?: string; - message?: Message; - app_permissions: string; - entitlements?: object[]; // TODO: types? - entitlement_sku_ids?: Snowflake[]; // DEPRECATED - authorizing_integration_owners?: Record; // TODO: types? - context?: number; - attachment_size_limit: number; + version: number; // TODO: types? + id: Snowflake; + application_id: Snowflake; + type: InteractionType; + token: string; + data?: object; // TODO: types? + guild?: InteractionGuild; + guild_id?: Snowflake; + guild_locale?: string; + channel?: Channel; + channel_id?: Snowflake; + member?: PublicMember; + user?: PublicUser; + locale?: string; + message?: Message; + app_permissions: string; + entitlements?: object[]; // TODO: types? + entitlement_sku_ids?: Snowflake[]; // DEPRECATED + authorizing_integration_owners?: Record; // TODO: types? + context?: number; + attachment_size_limit: number; } interface InteractionGuild { - id: Snowflake; - features: string[]; - locale: string; + id: Snowflake; + features: string[]; + locale: string; } diff --git a/src/schemas/api/bots/InteractionSchema.ts b/src/schemas/api/bots/InteractionSchema.ts index 9a8c824..da3c3c1 100644 --- a/src/schemas/api/bots/InteractionSchema.ts +++ b/src/schemas/api/bots/InteractionSchema.ts @@ -2,27 +2,27 @@ import { InteractionType } from "@spacebar/util"; export interface InteractionSchema { - type: InteractionType; - application_id: Snowflake; - guild_id?: Snowflake; - channel_id: Snowflake; - message_id?: Snowflake; - message_flags?: number; - session_id?: string; - data: InteractionData; - files?: object[]; // idk the type - nonce?: string; - analytics_location?: string; - section_name?: string; - source?: string; + type: InteractionType; + application_id: Snowflake; + guild_id?: Snowflake; + channel_id: Snowflake; + message_id?: Snowflake; + message_flags?: number; + session_id?: string; + data: InteractionData; + files?: object[]; // idk the type + nonce?: string; + analytics_location?: string; + section_name?: string; + source?: string; } interface InteractionData { - application_command: object; - attachments: UploadAttachmentRequestSchema[]; - id: string; - name: string; - options: ApplicationCommandOption[]; - type: number; - version: string; + application_command: object; + attachments: UploadAttachmentRequestSchema[]; + id: string; + name: string; + options: ApplicationCommandOption[]; + type: number; + version: string; } diff --git a/src/schemas/api/bots/SendableApplicationCommandDataSchema.ts b/src/schemas/api/bots/SendableApplicationCommandDataSchema.ts index 7dfbbd0..3f5496e 100644 --- a/src/schemas/api/bots/SendableApplicationCommandDataSchema.ts +++ b/src/schemas/api/bots/SendableApplicationCommandDataSchema.ts @@ -3,12 +3,12 @@ import { ApplicationCommandType } from "./ApplicationCommandSchema"; export interface SendableApplicationCommandDataSchema { - id: Snowflake; - type?: ApplicationCommandType; - name: string; - version: Snowflake; - application_command?: object; - options?: ApplicationCommandOption[]; - target_id?: Snowflake; - attachments?: object[]; // idk the type + id: Snowflake; + type?: ApplicationCommandType; + name: string; + version: Snowflake; + application_command?: object; + options?: ApplicationCommandOption[]; + target_id?: Snowflake; + attachments?: object[]; // idk the type } diff --git a/src/schemas/api/bots/SendableMessageComponentDataSchema.ts b/src/schemas/api/bots/SendableMessageComponentDataSchema.ts index 6581fa7..eb8d2ee 100644 --- a/src/schemas/api/bots/SendableMessageComponentDataSchema.ts +++ b/src/schemas/api/bots/SendableMessageComponentDataSchema.ts @@ -3,8 +3,8 @@ import { ApplicationCommandType } from "./ApplicationCommandSchema"; export interface SendableMessageComponentDataSchema { - component_type?: MessageComponentType; - type?: ApplicationCommandType; - custom_id: string; - values?: Snowflake[] | string[]; + component_type?: MessageComponentType; + type?: ApplicationCommandType; + custom_id: string; + values?: Snowflake[] | string[]; } diff --git a/src/schemas/api/bots/SendableModalSubmitDataSchema.ts b/src/schemas/api/bots/SendableModalSubmitDataSchema.ts index 33c4b00..e2c64cd 100644 --- a/src/schemas/api/bots/SendableModalSubmitDataSchema.ts +++ b/src/schemas/api/bots/SendableModalSubmitDataSchema.ts @@ -2,8 +2,8 @@ import { Attachment, Snowflake } from "@spacebar/util"; export interface SendableModalSubmitDataSchema { - id: Snowflake; - custom_id: string; - // components: ModalSubmitComponentData[]; // TODO: do this - attachments?: UploadAttachmentRequestSchema[]; + id: Snowflake; + custom_id: string; + // components: ModalSubmitComponentData[]; // TODO: do this + attachments?: UploadAttachmentRequestSchema[]; } diff --git a/src/schemas/api/channels/Channel.ts b/src/schemas/api/channels/Channel.ts index 7e44729..1065319 100644 --- a/src/schemas/api/channels/Channel.ts +++ b/src/schemas/api/channels/Channel.ts @@ -2,68 +2,68 @@ import { HTTPError } from "lambert-server"; export enum ChannelType { - GUILD_TEXT = 0, // a text channel within a guild - DM = 1, // a direct message between users - GUILD_VOICE = 2, // a voice channel within a guild - GROUP_DM = 3, // a direct message between multiple users - GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels - GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route - GUILD_STORE = 6, // a channel in which game developers can sell their things - ENCRYPTED = 7, // end-to-end encrypted channel - ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel - TRANSACTIONAL = 9, // event chain style transactional channel - GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel - GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel - GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission - GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience - DIRECTORY = 14, // guild directory listing channel - GUILD_FORUM = 15, // forum composed of IM threads - TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12 - KANBAN = 34, // confluence like kanban board - VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage) - CUSTOM_START = 64, // start custom channel types from here - UNHANDLED = 255, // unhandled unowned pass-through channel type + GUILD_TEXT = 0, // a text channel within a guild + DM = 1, // a direct message between users + GUILD_VOICE = 2, // a voice channel within a guild + GROUP_DM = 3, // a direct message between multiple users + GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels + GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route + GUILD_STORE = 6, // a channel in which game developers can sell their things + ENCRYPTED = 7, // end-to-end encrypted channel + ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel + TRANSACTIONAL = 9, // event chain style transactional channel + GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel + GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel + GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission + GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience + DIRECTORY = 14, // guild directory listing channel + GUILD_FORUM = 15, // forum composed of IM threads + TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12 + KANBAN = 34, // confluence like kanban board + VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage) + CUSTOM_START = 64, // start custom channel types from here + UNHANDLED = 255, // unhandled unowned pass-through channel type } export interface ChannelPermissionOverwrite { - allow: string; - deny: string; - id: string; - type: ChannelPermissionOverwriteType; + allow: string; + deny: string; + id: string; + type: ChannelPermissionOverwriteType; } export enum ChannelPermissionOverwriteType { - role = 0, - member = 1, - group = 2, + role = 0, + member = 1, + group = 2, } export interface DMChannel extends Omit { - type: ChannelType.DM | ChannelType.GROUP_DM; - recipients: Recipient[]; + type: ChannelType.DM | ChannelType.GROUP_DM; + recipients: Recipient[]; } // TODO: probably more props export function isTextChannel(type: ChannelType): boolean { - switch (type) { - case ChannelType.GUILD_STORE: - case ChannelType.GUILD_STAGE_VOICE: - case ChannelType.GUILD_CATEGORY: - case ChannelType.GUILD_FORUM: - case ChannelType.DIRECTORY: - throw new HTTPError("not a text channel", 400); - case ChannelType.DM: - case ChannelType.GROUP_DM: - case ChannelType.GUILD_NEWS: - case ChannelType.GUILD_VOICE: - case ChannelType.GUILD_NEWS_THREAD: - case ChannelType.GUILD_PUBLIC_THREAD: - case ChannelType.GUILD_PRIVATE_THREAD: - case ChannelType.GUILD_TEXT: - case ChannelType.ENCRYPTED: - case ChannelType.ENCRYPTED_THREAD: - return true; - default: - throw new HTTPError("unimplemented", 400); - } + switch (type) { + case ChannelType.GUILD_STORE: + case ChannelType.GUILD_STAGE_VOICE: + case ChannelType.GUILD_CATEGORY: + case ChannelType.GUILD_FORUM: + case ChannelType.DIRECTORY: + throw new HTTPError("not a text channel", 400); + case ChannelType.DM: + case ChannelType.GROUP_DM: + case ChannelType.GUILD_NEWS: + case ChannelType.GUILD_VOICE: + case ChannelType.GUILD_NEWS_THREAD: + case ChannelType.GUILD_PUBLIC_THREAD: + case ChannelType.GUILD_PRIVATE_THREAD: + case ChannelType.GUILD_TEXT: + case ChannelType.ENCRYPTED: + case ChannelType.ENCRYPTED_THREAD: + return true; + default: + throw new HTTPError("unimplemented", 400); + } } diff --git a/src/schemas/api/channels/Webhook.ts b/src/schemas/api/channels/Webhook.ts index 510916c..ac50001 100644 --- a/src/schemas/api/channels/Webhook.ts +++ b/src/schemas/api/channels/Webhook.ts @@ -1,5 +1,5 @@ export enum WebhookType { - Incoming = 1, - ChannelFollower = 2, - Application = 3, + Incoming = 1, + ChannelFollower = 2, + Application = 3, } diff --git a/src/schemas/api/developers/Application.ts b/src/schemas/api/developers/Application.ts index 8ef679e..10e49a6 100644 --- a/src/schemas/api/developers/Application.ts +++ b/src/schemas/api/developers/Application.ts @@ -17,46 +17,46 @@ */ export interface ApplicationCommand { - id: string; - application_id: string; - name: string; - description: string; - options?: ApplicationCommandOption[]; + id: string; + application_id: string; + name: string; + description: string; + options?: ApplicationCommandOption[]; } export interface ApplicationCommandOption { - type: ApplicationCommandOptionType; - name: string; - description: string; - required?: boolean; - choices?: ApplicationCommandOptionChoice[]; - options?: ApplicationCommandOption[]; + type: ApplicationCommandOptionType; + name: string; + description: string; + required?: boolean; + choices?: ApplicationCommandOptionChoice[]; + options?: ApplicationCommandOption[]; } export interface ApplicationCommandOptionChoice { - name: string; - value: string | number; + name: string; + value: string | number; } export enum ApplicationCommandOptionType { - SUB_COMMAND = 1, - SUB_COMMAND_GROUP = 2, - STRING = 3, - INTEGER = 4, - BOOLEAN = 5, - USER = 6, - CHANNEL = 7, - ROLE = 8, + SUB_COMMAND = 1, + SUB_COMMAND_GROUP = 2, + STRING = 3, + INTEGER = 4, + BOOLEAN = 5, + USER = 6, + CHANNEL = 7, + ROLE = 8, } export interface ApplicationCommandInteractionData { - id: string; - name: string; - options?: ApplicationCommandInteractionDataOption[]; + id: string; + name: string; + options?: ApplicationCommandInteractionDataOption[]; } export interface ApplicationCommandInteractionDataOption { - name: string; - value?: unknown; - options?: ApplicationCommandInteractionDataOption[]; + name: string; + value?: unknown; + options?: ApplicationCommandInteractionDataOption[]; } diff --git a/src/schemas/api/developers/ApplicationCreateSchema.ts b/src/schemas/api/developers/ApplicationCreateSchema.ts index 80956ec..37fd02a 100644 --- a/src/schemas/api/developers/ApplicationCreateSchema.ts +++ b/src/schemas/api/developers/ApplicationCreateSchema.ts @@ -17,6 +17,6 @@ */ export interface ApplicationCreateSchema { - name: string; - team_id?: string; + name: string; + team_id?: string; } diff --git a/src/schemas/api/developers/ApplicationModifySchema.ts b/src/schemas/api/developers/ApplicationModifySchema.ts index a871797..c550660 100644 --- a/src/schemas/api/developers/ApplicationModifySchema.ts +++ b/src/schemas/api/developers/ApplicationModifySchema.ts @@ -17,22 +17,22 @@ */ export interface ApplicationModifySchema { - description?: string; - icon?: string; - cover_image?: string; - interactions_endpoint_url?: string; - max_participants?: number | null; - name?: string; - privacy_policy_url?: string; - role_connections_verification_url?: string; - tags?: string[]; - terms_of_service_url?: string; - bot_public?: boolean; - bot_require_code_grant?: boolean; - flags?: number; - custom_install_url?: string; - guild_id?: string; - /*install_params?: { TODO: Validation + description?: string; + icon?: string; + cover_image?: string; + interactions_endpoint_url?: string; + max_participants?: number | null; + name?: string; + privacy_policy_url?: string; + role_connections_verification_url?: string; + tags?: string[]; + terms_of_service_url?: string; + bot_public?: boolean; + bot_require_code_grant?: boolean; + flags?: number; + custom_install_url?: string; + guild_id?: string; + /*install_params?: { TODO: Validation scopes: string[]; permissions: string; };*/ diff --git a/src/schemas/api/developers/Team.ts b/src/schemas/api/developers/Team.ts index 131e9fb..f29191c 100644 --- a/src/schemas/api/developers/Team.ts +++ b/src/schemas/api/developers/Team.ts @@ -1,9 +1,9 @@ export enum TeamMemberState { - INVITED = 1, - ACCEPTED = 2, + INVITED = 1, + ACCEPTED = 2, } export enum TeamMemberRole { - ADMIN = "admin", - DEVELOPER = "developer", - READ_ONLY = "read_only", + ADMIN = "admin", + DEVELOPER = "developer", + READ_ONLY = "read_only", } diff --git a/src/schemas/api/guilds/AuditLog.ts b/src/schemas/api/guilds/AuditLog.ts index 0f8ee63..9908d73 100644 --- a/src/schemas/api/guilds/AuditLog.ts +++ b/src/schemas/api/guilds/AuditLog.ts @@ -19,154 +19,154 @@ import { ChannelPermissionOverwrite } from "@spacebar/schemas"; export enum AuditLogEvents { - // guild level - GUILD_UPDATE = 1, - GUILD_IMPORT = 2, - GUILD_EXPORTED = 3, - GUILD_ARCHIVE = 4, - GUILD_UNARCHIVE = 5, - // join-leave - USER_JOIN = 6, - USER_LEAVE = 7, - // channels - CHANNEL_CREATE = 10, - CHANNEL_UPDATE = 11, - CHANNEL_DELETE = 12, - // permission overrides - CHANNEL_OVERWRITE_CREATE = 13, - CHANNEL_OVERWRITE_UPDATE = 14, - CHANNEL_OVERWRITE_DELETE = 15, - // kick and ban - MEMBER_KICK = 20, - MEMBER_PRUNE = 21, - MEMBER_BAN_ADD = 22, - MEMBER_BAN_REMOVE = 23, - // member updates - MEMBER_UPDATE = 24, - MEMBER_ROLE_UPDATE = 25, - MEMBER_MOVE = 26, - MEMBER_DISCONNECT = 27, - BOT_ADD = 28, - // roles - ROLE_CREATE = 30, - ROLE_UPDATE = 31, - ROLE_DELETE = 32, - ROLE_SWAP = 33, - // invites - INVITE_CREATE = 40, - INVITE_UPDATE = 41, - INVITE_DELETE = 42, - // webhooks - WEBHOOK_CREATE = 50, - WEBHOOK_UPDATE = 51, - WEBHOOK_DELETE = 52, - WEBHOOK_SWAP = 53, - // custom emojis - EMOJI_CREATE = 60, - EMOJI_UPDATE = 61, - EMOJI_DELETE = 62, - EMOJI_SWAP = 63, - // deletion - MESSAGE_CREATE = 70, // messages sent using non-primary seat of the user only - MESSAGE_EDIT = 71, // non-self edits only - MESSAGE_DELETE = 72, - MESSAGE_BULK_DELETE = 73, - // pinning - MESSAGE_PIN = 74, - MESSAGE_UNPIN = 75, - // integrations - INTEGRATION_CREATE = 80, - INTEGRATION_UPDATE = 81, - INTEGRATION_DELETE = 82, - // stage actions - STAGE_INSTANCE_CREATE = 83, - STAGE_INSTANCE_UPDATE = 84, - STAGE_INSTANCE_DELETE = 85, - // stickers - STICKER_CREATE = 90, - STICKER_UPDATE = 91, - STICKER_DELETE = 92, - STICKER_SWAP = 93, - // threads - THREAD_CREATE = 110, - THREAD_UPDATE = 111, - THREAD_DELETE = 112, - // application commands - APPLICATION_COMMAND_PERMISSION_UPDATE = 121, - // automod - POLICY_CREATE = 140, - POLICY_UPDATE = 141, - POLICY_DELETE = 142, - MESSAGE_BLOCKED_BY_POLICIES = 143, // in spacebar, blocked messages are stealth-dropped - // instance policies affecting the guild - GUILD_AFFECTED_BY_POLICIES = 216, - // message moves - IN_GUILD_MESSAGE_MOVE = 223, - CROSS_GUILD_MESSAGE_MOVE = 224, - // message routing - ROUTE_CREATE = 225, - ROUTE_UPDATE = 226, + // guild level + GUILD_UPDATE = 1, + GUILD_IMPORT = 2, + GUILD_EXPORTED = 3, + GUILD_ARCHIVE = 4, + GUILD_UNARCHIVE = 5, + // join-leave + USER_JOIN = 6, + USER_LEAVE = 7, + // channels + CHANNEL_CREATE = 10, + CHANNEL_UPDATE = 11, + CHANNEL_DELETE = 12, + // permission overrides + CHANNEL_OVERWRITE_CREATE = 13, + CHANNEL_OVERWRITE_UPDATE = 14, + CHANNEL_OVERWRITE_DELETE = 15, + // kick and ban + MEMBER_KICK = 20, + MEMBER_PRUNE = 21, + MEMBER_BAN_ADD = 22, + MEMBER_BAN_REMOVE = 23, + // member updates + MEMBER_UPDATE = 24, + MEMBER_ROLE_UPDATE = 25, + MEMBER_MOVE = 26, + MEMBER_DISCONNECT = 27, + BOT_ADD = 28, + // roles + ROLE_CREATE = 30, + ROLE_UPDATE = 31, + ROLE_DELETE = 32, + ROLE_SWAP = 33, + // invites + INVITE_CREATE = 40, + INVITE_UPDATE = 41, + INVITE_DELETE = 42, + // webhooks + WEBHOOK_CREATE = 50, + WEBHOOK_UPDATE = 51, + WEBHOOK_DELETE = 52, + WEBHOOK_SWAP = 53, + // custom emojis + EMOJI_CREATE = 60, + EMOJI_UPDATE = 61, + EMOJI_DELETE = 62, + EMOJI_SWAP = 63, + // deletion + MESSAGE_CREATE = 70, // messages sent using non-primary seat of the user only + MESSAGE_EDIT = 71, // non-self edits only + MESSAGE_DELETE = 72, + MESSAGE_BULK_DELETE = 73, + // pinning + MESSAGE_PIN = 74, + MESSAGE_UNPIN = 75, + // integrations + INTEGRATION_CREATE = 80, + INTEGRATION_UPDATE = 81, + INTEGRATION_DELETE = 82, + // stage actions + STAGE_INSTANCE_CREATE = 83, + STAGE_INSTANCE_UPDATE = 84, + STAGE_INSTANCE_DELETE = 85, + // stickers + STICKER_CREATE = 90, + STICKER_UPDATE = 91, + STICKER_DELETE = 92, + STICKER_SWAP = 93, + // threads + THREAD_CREATE = 110, + THREAD_UPDATE = 111, + THREAD_DELETE = 112, + // application commands + APPLICATION_COMMAND_PERMISSION_UPDATE = 121, + // automod + POLICY_CREATE = 140, + POLICY_UPDATE = 141, + POLICY_DELETE = 142, + MESSAGE_BLOCKED_BY_POLICIES = 143, // in spacebar, blocked messages are stealth-dropped + // instance policies affecting the guild + GUILD_AFFECTED_BY_POLICIES = 216, + // message moves + IN_GUILD_MESSAGE_MOVE = 223, + CROSS_GUILD_MESSAGE_MOVE = 224, + // message routing + ROUTE_CREATE = 225, + ROUTE_UPDATE = 226, } export interface AuditLogChange { - new_value?: AuditLogChangeValue; - old_value?: AuditLogChangeValue; - key: string; + new_value?: AuditLogChangeValue; + old_value?: AuditLogChangeValue; + key: string; } export interface AuditLogChangeValue { - name?: string; - description?: string; - icon_hash?: string; - splash_hash?: string; - discovery_splash_hash?: string; - banner_hash?: string; - owner_id?: string; - region?: string; - preferred_locale?: string; - afk_channel_id?: string; - afk_timeout?: number; - rules_channel_id?: string; - public_updates_channel_id?: string; - mfa_level?: number; - verification_level?: number; - explicit_content_filter?: number; - default_message_notifications?: number; - vanity_url_code?: string; - $add?: object[]; // TODO: These types are bad. - $remove?: object[]; - prune_delete_days?: number; - widget_enabled?: boolean; - widget_channel_id?: string; - system_channel_id?: string; - position?: number; - topic?: string; - bitrate?: number; - permission_overwrites?: ChannelPermissionOverwrite[]; - nsfw?: boolean; - application_id?: string; - rate_limit_per_user?: number; - permissions?: string; - color?: number; - hoist?: boolean; - mentionable?: boolean; - allow?: string; - deny?: string; - code?: string; - channel_id?: string; - inviter_id?: string; - max_uses?: number; - uses?: number; - max_age?: number; - temporary?: boolean; - deaf?: boolean; - mute?: boolean; - nick?: string; - avatar_hash?: string; - id?: string; - type?: number; - enable_emoticons?: boolean; - expire_behavior?: number; - expire_grace_period?: number; - user_limit?: number; + name?: string; + description?: string; + icon_hash?: string; + splash_hash?: string; + discovery_splash_hash?: string; + banner_hash?: string; + owner_id?: string; + region?: string; + preferred_locale?: string; + afk_channel_id?: string; + afk_timeout?: number; + rules_channel_id?: string; + public_updates_channel_id?: string; + mfa_level?: number; + verification_level?: number; + explicit_content_filter?: number; + default_message_notifications?: number; + vanity_url_code?: string; + $add?: object[]; // TODO: These types are bad. + $remove?: object[]; + prune_delete_days?: number; + widget_enabled?: boolean; + widget_channel_id?: string; + system_channel_id?: string; + position?: number; + topic?: string; + bitrate?: number; + permission_overwrites?: ChannelPermissionOverwrite[]; + nsfw?: boolean; + application_id?: string; + rate_limit_per_user?: number; + permissions?: string; + color?: number; + hoist?: boolean; + mentionable?: boolean; + allow?: string; + deny?: string; + code?: string; + channel_id?: string; + inviter_id?: string; + max_uses?: number; + uses?: number; + max_age?: number; + temporary?: boolean; + deaf?: boolean; + mute?: boolean; + nick?: string; + avatar_hash?: string; + id?: string; + type?: number; + enable_emoticons?: boolean; + expire_behavior?: number; + expire_grace_period?: number; + user_limit?: number; } diff --git a/src/schemas/api/guilds/Automod.ts b/src/schemas/api/guilds/Automod.ts index e7c3ab9..1c66377 100644 --- a/src/schemas/api/guilds/Automod.ts +++ b/src/schemas/api/guilds/Automod.ts @@ -21,75 +21,75 @@ export type AutomodRuleTriggerMetadata = AutomodMentionSpamRule | AutomodSuspectedSpamRule | AutomodCommonlyFlaggedWordsRule | AutomodCustomWordsRule; export class AutomodMentionSpamRule { - mention_total_limit: number; - mention_raid_protection_enabled: boolean; + mention_total_limit: number; + mention_raid_protection_enabled: boolean; } export class AutomodSuspectedSpamRule {} export class AutomodCommonlyFlaggedWordsRule { - allow_list: string[]; - presets: AutomodKeywordPresetType[]; + allow_list: string[]; + presets: AutomodKeywordPresetType[]; } export class AutomodCustomWordsRule { - allow_list: string[]; - keyword_filter: string[]; - regex_patterns: string[]; + allow_list: string[]; + keyword_filter: string[]; + regex_patterns: string[]; } export enum AutomodRuleEventType { - MESSAGE_SEND = 1, - GUILD_MEMBER_EVENT = 2, + MESSAGE_SEND = 1, + GUILD_MEMBER_EVENT = 2, } export enum AutomodRuleTriggerType { - KEYWORD = 1, - HARMFUL_LINK = 2, - SPAM = 3, - KEYWORD_PRESET = 4, - MENTION_SPAM = 5, - USER_PROFILE = 6, - GUILD_POLICY = 7, + KEYWORD = 1, + HARMFUL_LINK = 2, + SPAM = 3, + KEYWORD_PRESET = 4, + MENTION_SPAM = 5, + USER_PROFILE = 6, + GUILD_POLICY = 7, } export enum AutomodKeywordPresetType { - PROFANITY = 1, - SEXUAL_CONTENT = 2, - SLURS = 3, + PROFANITY = 1, + SEXUAL_CONTENT = 2, + SLURS = 3, } export enum AutomodRuleActionType { - BLOCK_MESSAGE = 1, - SEND_ALERT_MESSAGE = 2, - TIMEOUT_USER = 3, - QUARANTINE_USER = 4, + BLOCK_MESSAGE = 1, + SEND_ALERT_MESSAGE = 2, + TIMEOUT_USER = 3, + QUARANTINE_USER = 4, } export type AutomodAction = - | { - type: AutomodRuleActionType.BLOCK_MESSAGE; - metadata: { - custom_message?: string; - }; - } - | { - type: AutomodRuleActionType.SEND_ALERT_MESSAGE; - metadata: { - channel_id: Snowflake; - }; - } - | { - type: AutomodRuleActionType.TIMEOUT_USER; - metadata: { - duration_seconds: number; - }; - } - | { - type: AutomodRuleActionType.QUARANTINE_USER; - metadata: { - duration_seconds: number; - }; - }; + | { + type: AutomodRuleActionType.BLOCK_MESSAGE; + metadata: { + custom_message?: string; + }; + } + | { + type: AutomodRuleActionType.SEND_ALERT_MESSAGE; + metadata: { + channel_id: Snowflake; + }; + } + | { + type: AutomodRuleActionType.TIMEOUT_USER; + metadata: { + duration_seconds: number; + }; + } + | { + type: AutomodRuleActionType.QUARANTINE_USER; + metadata: { + duration_seconds: number; + }; + }; // TODO // eslint-disable-next-line diff --git a/src/schemas/api/guilds/GuildProfileResponse.ts b/src/schemas/api/guilds/GuildProfileResponse.ts index 703967c..a71a18a 100644 --- a/src/schemas/api/guilds/GuildProfileResponse.ts +++ b/src/schemas/api/guilds/GuildProfileResponse.ts @@ -17,79 +17,79 @@ */ export interface GuildProfileResponse { - id: string; - name: string; - icon_hash: string | null; - member_count: number; - online_count: number; - description: string; - brand_color_primary: string; - banner_hash: string | null; - game_application_ids: string[]; - game_activity: { [id: string]: GameActivity }; - tag: string | null; - badge: GuildBadgeType; - badge_color_primary: string; - badge_color_secondary: string; - badge_hash: string; - traits: GuildTrait[]; - features: string[]; - visibility: GuildVisibilityLevel; - custom_banner_hash: string | null; - premium_subscription_count: number; - premium_tier: number; + id: string; + name: string; + icon_hash: string | null; + member_count: number; + online_count: number; + description: string; + brand_color_primary: string; + banner_hash: string | null; + game_application_ids: string[]; + game_activity: { [id: string]: GameActivity }; + tag: string | null; + badge: GuildBadgeType; + badge_color_primary: string; + badge_color_secondary: string; + badge_hash: string; + traits: GuildTrait[]; + features: string[]; + visibility: GuildVisibilityLevel; + custom_banner_hash: string | null; + premium_subscription_count: number; + premium_tier: number; } export interface GameActivity { - activity_level: number; - activity_score: number; + activity_level: number; + activity_score: number; } export interface GuildTrait { - emoji_id: string | null; - emoji_name: string | null; - emoji_animated: boolean; - label: string; - position: number; + emoji_id: string | null; + emoji_name: string | null; + emoji_animated: boolean; + label: string; + position: number; } // TODO: move export enum GuildVisibilityLevel { - PUBLIC = 1, - RESTRICTED = 2, - PUBLIC_WITH_RECRUITMENT = 3, + PUBLIC = 1, + RESTRICTED = 2, + PUBLIC_WITH_RECRUITMENT = 3, } export enum GuildBadgeType { - SWORD = 0, - WATER_DROP = 1, - SKULL = 2, - TOADSTOOL = 3, - MOON = 4, - LIGHTNING = 5, - LEAF = 6, - HEART = 7, - FIRE = 8, - COMPASS = 9, - CROSSHAIRS = 10, - FLOWER = 11, - FORCE = 12, - GEM = 13, - LAVA = 14, - PSYCHIC = 15, - SMOKE = 16, - SNOW = 17, - SOUND = 18, - SUN = 19, - WIND = 20, - BUNNY = 21, - DOG = 22, - FROG = 23, - GOAT = 24, - CAT = 25, - DIAMOND = 26, - CROWN = 27, - TROPHY = 28, - MONEY_BAG = 29, - DOLLAR_SIGN = 30, + SWORD = 0, + WATER_DROP = 1, + SKULL = 2, + TOADSTOOL = 3, + MOON = 4, + LIGHTNING = 5, + LEAF = 6, + HEART = 7, + FIRE = 8, + COMPASS = 9, + CROSSHAIRS = 10, + FLOWER = 11, + FORCE = 12, + GEM = 13, + LAVA = 14, + PSYCHIC = 15, + SMOKE = 16, + SNOW = 17, + SOUND = 18, + SUN = 19, + WIND = 20, + BUNNY = 21, + DOG = 22, + FROG = 23, + GOAT = 24, + CAT = 25, + DIAMOND = 26, + CROWN = 27, + TROPHY = 28, + MONEY_BAG = 29, + DOLLAR_SIGN = 30, } diff --git a/src/schemas/api/guilds/GuildSchema.ts b/src/schemas/api/guilds/GuildSchema.ts index 320ee36..adc9efd 100644 --- a/src/schemas/api/guilds/GuildSchema.ts +++ b/src/schemas/api/guilds/GuildSchema.ts @@ -19,6 +19,6 @@ import { Snowflake } from "@spacebar/schemas"; export interface GuildAvailableSchema { - id: Snowflake; - available: boolean; + id: Snowflake; + available: boolean; } diff --git a/src/schemas/api/guilds/Role.ts b/src/schemas/api/guilds/Role.ts index f9b0100..2f32e5d 100644 --- a/src/schemas/api/guilds/Role.ts +++ b/src/schemas/api/guilds/Role.ts @@ -1,13 +1,13 @@ export class RoleColors { - primary_color: number; - secondary_color: number | undefined; // only used for "holographic" and "gradient" styles - tertiary_color?: number | undefined; // only used for "holographic" style + primary_color: number; + secondary_color: number | undefined; // only used for "holographic" and "gradient" styles + tertiary_color?: number | undefined; // only used for "holographic" style - toJSON(): RoleColors { - return { - ...this, - secondary_color: this.secondary_color ?? undefined, - tertiary_color: this.tertiary_color ?? undefined, - }; - } + toJSON(): RoleColors { + return { + ...this, + secondary_color: this.secondary_color ?? undefined, + tertiary_color: this.tertiary_color ?? undefined, + }; + } } diff --git a/src/schemas/api/guilds/Sticker.ts b/src/schemas/api/guilds/Sticker.ts index b02e1e2..116d72f 100644 --- a/src/schemas/api/guilds/Sticker.ts +++ b/src/schemas/api/guilds/Sticker.ts @@ -1,11 +1,11 @@ export enum StickerType { - STANDARD = 1, - GUILD = 2, + STANDARD = 1, + GUILD = 2, } export enum StickerFormatType { - PNG = 1, - APNG = 2, - LOTTIE = 3, - GIF = 4, + PNG = 1, + APNG = 2, + LOTTIE = 3, + GIF = 4, } diff --git a/src/schemas/api/guilds/VoiceState.ts b/src/schemas/api/guilds/VoiceState.ts index b2275c7..9a8fb2b 100644 --- a/src/schemas/api/guilds/VoiceState.ts +++ b/src/schemas/api/guilds/VoiceState.ts @@ -1,18 +1,18 @@ import { VoiceState } from "@spacebar/util"; export enum PublicVoiceStateEnum { - user_id, - suppress, - session_id, - self_video, - self_mute, - self_deaf, - self_stream, - request_to_speak_timestamp, - mute, - deaf, - channel_id, - guild_id, + user_id, + suppress, + session_id, + self_video, + self_mute, + self_deaf, + self_stream, + request_to_speak_timestamp, + mute, + deaf, + channel_id, + guild_id, } export type PublicVoiceStateKeys = keyof typeof PublicVoiceStateEnum; diff --git a/src/schemas/api/messages/Components.ts b/src/schemas/api/messages/Components.ts index 96929ac..843a15c 100644 --- a/src/schemas/api/messages/Components.ts +++ b/src/schemas/api/messages/Components.ts @@ -19,93 +19,93 @@ import { PartialEmoji } from "@spacebar/schemas"; export interface MessageComponent { - type: MessageComponentType; + type: MessageComponentType; } export interface ActionRowComponent extends MessageComponent { - type: MessageComponentType.ActionRow; - components: (ButtonComponent | StringSelectMenuComponent | SelectMenuComponent | TextInputComponent)[]; + type: MessageComponentType.ActionRow; + components: (ButtonComponent | StringSelectMenuComponent | SelectMenuComponent | TextInputComponent)[]; } export interface ButtonComponent extends MessageComponent { - type: MessageComponentType.Button; - style: ButtonStyle; - label?: string; - emoji?: PartialEmoji; - custom_id?: string; - sku_id?: string; - url?: string; - disabled?: boolean; + type: MessageComponentType.Button; + style: ButtonStyle; + label?: string; + emoji?: PartialEmoji; + custom_id?: string; + sku_id?: string; + url?: string; + disabled?: boolean; } export enum ButtonStyle { - Primary = 1, - Secondary = 2, - Success = 3, - Danger = 4, - Link = 5, - Premium = 6, + Primary = 1, + Secondary = 2, + Success = 3, + Danger = 4, + Link = 5, + Premium = 6, } export interface SelectMenuComponent extends MessageComponent { - type: - | MessageComponentType.StringSelect - | MessageComponentType.UserSelect - | MessageComponentType.RoleSelect - | MessageComponentType.MentionableSelect - | MessageComponentType.ChannelSelect; - custom_id: string; - channel_types?: number[]; - placeholder?: string; - default_values?: SelectMenuDefaultOption[]; // only for non-string selects - min_values?: number; - max_values?: number; - disabled?: boolean; + type: + | MessageComponentType.StringSelect + | MessageComponentType.UserSelect + | MessageComponentType.RoleSelect + | MessageComponentType.MentionableSelect + | MessageComponentType.ChannelSelect; + custom_id: string; + channel_types?: number[]; + placeholder?: string; + default_values?: SelectMenuDefaultOption[]; // only for non-string selects + min_values?: number; + max_values?: number; + disabled?: boolean; } export interface SelectMenuOption { - label: string; - value: string; - description?: string; - emoji?: PartialEmoji; - default?: boolean; + label: string; + value: string; + description?: string; + emoji?: PartialEmoji; + default?: boolean; } export interface SelectMenuDefaultOption { - id: string; - type: "user" | "role" | "channel"; + id: string; + type: "user" | "role" | "channel"; } export interface StringSelectMenuComponent extends SelectMenuComponent { - type: MessageComponentType.StringSelect; - options: SelectMenuOption[]; + type: MessageComponentType.StringSelect; + options: SelectMenuOption[]; } export interface TextInputComponent extends MessageComponent { - type: MessageComponentType.TextInput; - custom_id: string; - style: TextInputStyle; - label: string; - min_length?: number; - max_length?: number; - required?: boolean; - value?: string; - placeholder?: string; + type: MessageComponentType.TextInput; + custom_id: string; + style: TextInputStyle; + label: string; + min_length?: number; + max_length?: number; + required?: boolean; + value?: string; + placeholder?: string; } export enum TextInputStyle { - Short = 1, - Paragraph = 2, + Short = 1, + Paragraph = 2, } export enum MessageComponentType { - Script = 0, // self command script - ActionRow = 1, - Button = 2, - StringSelect = 3, - TextInput = 4, - UserSelect = 5, - RoleSelect = 6, - MentionableSelect = 7, - ChannelSelect = 8, + Script = 0, // self command script + ActionRow = 1, + Button = 2, + StringSelect = 3, + TextInput = 4, + UserSelect = 5, + RoleSelect = 6, + MentionableSelect = 7, + ChannelSelect = 8, } diff --git a/src/schemas/api/messages/Embeds.ts b/src/schemas/api/messages/Embeds.ts index e97020b..0f6019a 100644 --- a/src/schemas/api/messages/Embeds.ts +++ b/src/schemas/api/messages/Embeds.ts @@ -17,49 +17,49 @@ */ export interface Embed { - title?: string; //title of embed - type?: EmbedType; // type of embed (always "rich" for webhook embeds) - description?: string; // description of embed - url?: string; // url of embed - timestamp?: Date; // timestamp of embed content - color?: number; // color code of the embed - footer?: { - text: string; - icon_url?: string; - proxy_icon_url?: string; - }; // footer object footer information - image?: EmbedImage; // image object image information - thumbnail?: EmbedImage; // thumbnail object thumbnail information - video?: EmbedImage; // video object video information - provider?: { - name?: string; - url?: string; - }; // provider object provider information - author?: { - name?: string; - url?: string; - icon_url?: string; - proxy_icon_url?: string; - }; // author object author information - fields?: { - name: string; - value: string; - inline?: boolean; - }[]; + title?: string; //title of embed + type?: EmbedType; // type of embed (always "rich" for webhook embeds) + description?: string; // description of embed + url?: string; // url of embed + timestamp?: Date; // timestamp of embed content + color?: number; // color code of the embed + footer?: { + text: string; + icon_url?: string; + proxy_icon_url?: string; + }; // footer object footer information + image?: EmbedImage; // image object image information + thumbnail?: EmbedImage; // thumbnail object thumbnail information + video?: EmbedImage; // video object video information + provider?: { + name?: string; + url?: string; + }; // provider object provider information + author?: { + name?: string; + url?: string; + icon_url?: string; + proxy_icon_url?: string; + }; // author object author information + fields?: { + name: string; + value: string; + inline?: boolean; + }[]; } export enum EmbedType { - rich = "rich", - image = "image", - video = "video", - gifv = "gifv", - article = "article", - link = "link", + rich = "rich", + image = "image", + video = "video", + gifv = "gifv", + article = "article", + link = "link", } export interface EmbedImage { - url?: string; - proxy_url?: string; - height?: number; - width?: number; + url?: string; + proxy_url?: string; + height?: number; + width?: number; } diff --git a/src/schemas/api/messages/Message.ts b/src/schemas/api/messages/Message.ts index 30eccc7..69984e2 100644 --- a/src/schemas/api/messages/Message.ts +++ b/src/schemas/api/messages/Message.ts @@ -19,31 +19,31 @@ import { PartialUser, Snowflake } from "@spacebar/schemas"; export enum MessageType { - DEFAULT = 0, - RECIPIENT_ADD = 1, - RECIPIENT_REMOVE = 2, - CALL = 3, - CHANNEL_NAME_CHANGE = 4, - CHANNEL_ICON_CHANGE = 5, - CHANNEL_PINNED_MESSAGE = 6, - GUILD_MEMBER_JOIN = 7, - USER_PREMIUM_GUILD_SUBSCRIPTION = 8, - USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9, - USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10, - USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11, - CHANNEL_FOLLOW_ADD = 12, - ACTION = 13, // /me messages - GUILD_DISCOVERY_DISQUALIFIED = 14, - GUILD_DISCOVERY_REQUALIFIED = 15, - ENCRYPTED = 16, - REPLY = 19, - APPLICATION_COMMAND = 20, // application command or self command invocation - ROUTE_ADDED = 41, // custom message routing: new route affecting that channel - ROUTE_DISABLED = 42, // custom message routing: given route no longer affecting that channel - SELF_COMMAND_SCRIPT = 43, // self command scripts - ENCRYPTION = 50, - CUSTOM_START = 63, - UNHANDLED = 255, + DEFAULT = 0, + RECIPIENT_ADD = 1, + RECIPIENT_REMOVE = 2, + CALL = 3, + CHANNEL_NAME_CHANGE = 4, + CHANNEL_ICON_CHANGE = 5, + CHANNEL_PINNED_MESSAGE = 6, + GUILD_MEMBER_JOIN = 7, + USER_PREMIUM_GUILD_SUBSCRIPTION = 8, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11, + CHANNEL_FOLLOW_ADD = 12, + ACTION = 13, // /me messages + GUILD_DISCOVERY_DISQUALIFIED = 14, + GUILD_DISCOVERY_REQUALIFIED = 15, + ENCRYPTED = 16, + REPLY = 19, + APPLICATION_COMMAND = 20, // application command or self command invocation + ROUTE_ADDED = 41, // custom message routing: new route affecting that channel + ROUTE_DISABLED = 42, // custom message routing: given route no longer affecting that channel + SELF_COMMAND_SCRIPT = 43, // self command scripts + ENCRYPTION = 50, + CUSTOM_START = 63, + UNHANDLED = 255, } /** @@ -64,33 +64,33 @@ */ export interface PartialMessage { - id: Snowflake; - channel_id: string; - type: MessageType; - content: string; - author: PartialUser; - flags?: number; - application_id?: string; - // channel?: Channel; // TODO: ephemeral DM channels - // recipient_id?: string; // TODO: ephemeral DM channels + id: Snowflake; + channel_id: string; + type: MessageType; + content: string; + author: PartialUser; + flags?: number; + application_id?: string; + // channel?: Channel; // TODO: ephemeral DM channels + // recipient_id?: string; // TODO: ephemeral DM channels } export interface Reaction { - count: number; - //// not saved in the database // me: boolean; // whether the current user reacted using this emoji - emoji: PartialEmoji; - user_ids: Snowflake[]; + count: number; + //// not saved in the database // me: boolean; // whether the current user reacted using this emoji + emoji: PartialEmoji; + user_ids: Snowflake[]; } export interface PartialEmoji { - id?: string; - name: string; - animated?: boolean; + id?: string; + name: string; + animated?: boolean; } export interface AllowedMentions { - parse?: ("users" | "roles" | "everyone")[]; - roles?: Snowflake[]; - users?: Snowflake[]; - replied_user?: boolean; + parse?: ("users" | "roles" | "everyone")[]; + roles?: Snowflake[]; + users?: Snowflake[]; + replied_user?: boolean; } diff --git a/src/schemas/api/messages/Polls.ts b/src/schemas/api/messages/Polls.ts index d642388..85fe69b 100644 --- a/src/schemas/api/messages/Polls.ts +++ b/src/schemas/api/messages/Polls.ts @@ -19,30 +19,30 @@ import { PartialEmoji } from "@spacebar/schemas"; export interface Poll { - question: PollMedia; - answers: PollAnswer[]; - expiry: Date; - allow_multiselect: boolean; - results?: PollResult; + question: PollMedia; + answers: PollAnswer[]; + expiry: Date; + allow_multiselect: boolean; + results?: PollResult; } export interface PollMedia { - text?: string; - emoji?: PartialEmoji; + text?: string; + emoji?: PartialEmoji; } export interface PollAnswer { - answer_id?: string; - poll_media: PollMedia; + answer_id?: string; + poll_media: PollMedia; } export interface PollResult { - is_finalized: boolean; - answer_counts: PollAnswerCount[]; + is_finalized: boolean; + answer_counts: PollAnswerCount[]; } export interface PollAnswerCount { - id: string; - count: number; - me_voted: boolean; + id: string; + count: number; + me_voted: boolean; } diff --git a/src/schemas/api/users/InstanceUserDeleteSchema.ts b/src/schemas/api/users/InstanceUserDeleteSchema.ts index 923386d..497e334 100644 --- a/src/schemas/api/users/InstanceUserDeleteSchema.ts +++ b/src/schemas/api/users/InstanceUserDeleteSchema.ts @@ -2,6 +2,6 @@ export type InstanceUserDeleteSchema = InstanceUserDeleteSchemaContent | undefined; //unsure if this a correct way to make the body optional export interface InstanceUserDeleteSchemaContent { - reason?: string; - persistInstanceBan?: boolean; + reason?: string; + persistInstanceBan?: boolean; } diff --git a/src/schemas/api/users/Member.ts b/src/schemas/api/users/Member.ts index 025e4e1..cc2c334 100644 --- a/src/schemas/api/users/Member.ts +++ b/src/schemas/api/users/Member.ts @@ -2,94 +2,94 @@ import { Member } from "@spacebar/util"; export interface ChannelOverride { - message_notifications: number; - mute_config: MuteConfig; - muted: boolean; - channel_id: string | null; + message_notifications: number; + mute_config: MuteConfig; + muted: boolean; + channel_id: string | null; } export interface UserGuildSettings { - // channel_overrides: { - // channel_id: string; - // message_notifications: number; - // mute_config: MuteConfig; - // muted: boolean; - // }[]; + // channel_overrides: { + // channel_id: string; + // message_notifications: number; + // mute_config: MuteConfig; + // muted: boolean; + // }[]; - channel_overrides: { - [channel_id: string]: ChannelOverride; - } | null; - message_notifications: number; - mobile_push: boolean; - mute_config: MuteConfig | null; - muted: boolean; - suppress_everyone: boolean; - suppress_roles: boolean; - version: number; - guild_id: string | null; - flags: number; - mute_scheduled_events: boolean; - hide_muted_channels: boolean; - notify_highlights: 0; + channel_overrides: { + [channel_id: string]: ChannelOverride; + } | null; + message_notifications: number; + mobile_push: boolean; + mute_config: MuteConfig | null; + muted: boolean; + suppress_everyone: boolean; + suppress_roles: boolean; + version: number; + guild_id: string | null; + flags: number; + mute_scheduled_events: boolean; + hide_muted_channels: boolean; + notify_highlights: 0; } export const DefaultUserGuildSettings: UserGuildSettings = { - channel_overrides: null, - message_notifications: 1, - flags: 0, - hide_muted_channels: false, - mobile_push: true, - mute_config: null, - mute_scheduled_events: false, - muted: false, - notify_highlights: 0, - suppress_everyone: false, - suppress_roles: false, - version: 453, // ? - guild_id: null, + channel_overrides: null, + message_notifications: 1, + flags: 0, + hide_muted_channels: false, + mobile_push: true, + mute_config: null, + mute_scheduled_events: false, + muted: false, + notify_highlights: 0, + suppress_everyone: false, + suppress_roles: false, + version: 453, // ? + guild_id: null, }; export interface MuteConfig { - end_time: number; - selected_time_window: number; + end_time: number; + selected_time_window: number; } export type PublicMemberKeys = - | "id" - | "guild_id" - | "nick" - | "roles" - | "joined_at" - | "pending" - | "deaf" - | "mute" - | "premium_since" - | "avatar" - | "banner" - | "bio" - | "theme_colors" - | "pronouns" - | "communication_disabled_until"; + | "id" + | "guild_id" + | "nick" + | "roles" + | "joined_at" + | "pending" + | "deaf" + | "mute" + | "premium_since" + | "avatar" + | "banner" + | "bio" + | "theme_colors" + | "pronouns" + | "communication_disabled_until"; export const PublicMemberProjection: PublicMemberKeys[] = [ - "id", - "guild_id", - "nick", - "roles", - "joined_at", - "pending", - "deaf", - "mute", - "premium_since", - "avatar", - "banner", - "bio", - "theme_colors", - "pronouns", - "communication_disabled_until", + "id", + "guild_id", + "nick", + "roles", + "joined_at", + "pending", + "deaf", + "mute", + "premium_since", + "avatar", + "banner", + "bio", + "theme_colors", + "pronouns", + "communication_disabled_until", ]; export type PublicMember = Omit, "roles"> & { - user: PublicUser; - roles: string[]; // only role ids not objects + user: PublicUser; + roles: string[]; // only role ids not objects }; diff --git a/src/schemas/api/users/SessionsSchemas.ts b/src/schemas/api/users/SessionsSchemas.ts index b7c509b..3526414 100644 --- a/src/schemas/api/users/SessionsSchemas.ts +++ b/src/schemas/api/users/SessionsSchemas.ts @@ -23,19 +23,19 @@ export type GetSessionsResponse = { user_sessions: DeviceInfo[] }; export type DeviceInfo = { - id: string; - id_hash: string; - status: string; - activities: ActivitySchema["activities"][]; - client_status: ClientStatus; - approx_last_used_time: string; - client_info: { - client: string; - os: string; - version: number; - location: string; - }; - last_seen?: Date; - last_seen_ip?: string; - last_seen_location?: string; + id: string; + id_hash: string; + status: string; + activities: ActivitySchema["activities"][]; + client_status: ClientStatus; + approx_last_used_time: string; + client_info: { + client: string; + os: string; + version: number; + location: string; + }; + last_seen?: Date; + last_seen_ip?: string; + last_seen_location?: string; }; diff --git a/src/schemas/api/users/User.ts b/src/schemas/api/users/User.ts index 5de981e..5a13d3a 100644 --- a/src/schemas/api/users/User.ts +++ b/src/schemas/api/users/User.ts @@ -21,125 +21,125 @@ import { Relationship, Session } from "@spacebar/util/entities"; interface UserEntityPleaseRewriteThankYou { - id: Snowflake; - username: string; - discriminator: string; - avatar?: string; - accent_color?: number; - banner?: string; - theme_colors?: number[]; - pronouns?: string; - phone?: string; - desktop: boolean; - mobile: boolean; - premium: boolean; - premium_type: number; - bot: boolean; - bio: string; - system: boolean; - nsfw_allowed: boolean; - mfa_enabled: boolean; - webauthn_enabled: boolean; - created_at: Date; - premium_since: Date; - verified: boolean; - disabled: boolean; - deleted: boolean; - email?: string; - flags: number; - public_flags: number; - purchased_flags: number; - premium_usage_flags: number; - rights: string; - sessions: Session[]; - relationships: Relationship[]; - connected_accounts: ConnectedAccountSchema[]; - fingerprints: string[]; - settings?: UserSettingsSchema; - extended_settings: string; - badge_ids?: string[]; + id: Snowflake; + username: string; + discriminator: string; + avatar?: string; + accent_color?: number; + banner?: string; + theme_colors?: number[]; + pronouns?: string; + phone?: string; + desktop: boolean; + mobile: boolean; + premium: boolean; + premium_type: number; + bot: boolean; + bio: string; + system: boolean; + nsfw_allowed: boolean; + mfa_enabled: boolean; + webauthn_enabled: boolean; + created_at: Date; + premium_since: Date; + verified: boolean; + disabled: boolean; + deleted: boolean; + email?: string; + flags: number; + public_flags: number; + purchased_flags: number; + premium_usage_flags: number; + rights: string; + sessions: Session[]; + relationships: Relationship[]; + connected_accounts: ConnectedAccountSchema[]; + fingerprints: string[]; + settings?: UserSettingsSchema; + extended_settings: string; + badge_ids?: string[]; } export interface PartialUser { - id: Snowflake; - username: string; - discriminator: string; - global_name?: string | null; - avatar: string | null; - avatar_decoration_data?: AvatarDecorationData | null; - collectibles?: Collectibles | null; - display_name_styles?: DisplayNameStyle | null; - primary_guild?: PrimaryGuild | null; - bot?: boolean; - system?: boolean; - banner?: string | null; - accent_color?: number | null; - public_flags?: number; + id: Snowflake; + username: string; + discriminator: string; + global_name?: string | null; + avatar: string | null; + avatar_decoration_data?: AvatarDecorationData | null; + collectibles?: Collectibles | null; + display_name_styles?: DisplayNameStyle | null; + primary_guild?: PrimaryGuild | null; + bot?: boolean; + system?: boolean; + banner?: string | null; + accent_color?: number | null; + public_flags?: number; } export interface AvatarDecorationData { - asset: string; - sku_id: Snowflake; - expires_at: string | null; + asset: string; + sku_id: Snowflake; + expires_at: string | null; } export interface Collectibles { - nameplate: NameplateData | null; + nameplate: NameplateData | null; } export interface NameplateData { - asset: string; - sku_id: Snowflake; - label: string; - palette: string; - expires_at: number | null; + asset: string; + sku_id: Snowflake; + label: string; + palette: string; + expires_at: number | null; } export interface DisplayNameStyle { - font_id: number; - effect_id: number; - colors: number; + font_id: number; + effect_id: number; + colors: number; } export interface PrimaryGuild { - identity_enabled: boolean | null; - identity_guild_id: Snowflake | null; - tag: string | null; - badge: string | null; + identity_enabled: boolean | null; + identity_guild_id: Snowflake | null; + tag: string | null; + badge: string | null; } export enum PublicUserEnum { - username, - discriminator, - id, - public_flags, - avatar, - accent_color, - banner, - bio, - bot, - premium_since, - premium_type, - theme_colors, - pronouns, - badge_ids, + username, + discriminator, + id, + public_flags, + avatar, + accent_color, + banner, + bio, + bot, + premium_since, + premium_type, + theme_colors, + pronouns, + badge_ids, } export type PublicUserKeys = keyof typeof PublicUserEnum; export enum PrivateUserEnum { - flags, - mfa_enabled, - email, - phone, - verified, - nsfw_allowed, - premium, - premium_type, - purchased_flags, - premium_usage_flags, - disabled, - settings, - // locale + flags, + mfa_enabled, + email, + phone, + verified, + nsfw_allowed, + premium, + premium_type, + purchased_flags, + premium_usage_flags, + disabled, + settings, + // locale } export type PrivateUserKeys = keyof typeof PrivateUserEnum | PublicUserKeys; @@ -152,54 +152,54 @@ export type PrivateUser = Pick; export interface UserPrivate extends Pick { - locale: string; + locale: string; } // This causes a failure in openapi.js...? export class UserFlags extends BitField { - static FLAGS = { - DISCORD_EMPLOYEE: 1n << 0n, - PARTNERED_SERVER_OWNER: 1n << 1n, - HYPESQUAD_EVENTS: 1n << 2n, - BUGHUNTER_LEVEL_1: 1n << 3n, - MFA_SMS: 1n << 4n, - PREMIUM_PROMO_DISMISSED: 1n << 5n, - HOUSE_BRAVERY: 1n << 6n, - HOUSE_BRILLIANCE: 1n << 7n, - HOUSE_BALANCE: 1n << 8n, - EARLY_SUPPORTER: 1n << 9n, - TEAM_USER: 1n << 10n, - TRUST_AND_SAFETY: 1n << 11n, - SYSTEM: 1n << 12n, - HAS_UNREAD_URGENT_MESSAGES: 1n << 13n, - BUGHUNTER_LEVEL_2: 1n << 14n, - UNDERAGE_DELETED: 1n << 15n, - VERIFIED_BOT: 1n << 16n, - EARLY_VERIFIED_BOT_DEVELOPER: 1n << 17n, - CERTIFIED_MODERATOR: 1n << 18n, - BOT_HTTP_INTERACTIONS: 1n << 19n, - SPAMMER: 1n << 20n, - // @deprecated - DISABLE_PREMIUM: 1n << 21n, - ACTIVE_DEVELOPER: 1n << 22n, - PROVISIONAL_ACCOUNT: 1n << 23n, - // Where did 24-32 go??? - HIGH_GLOBAL_RATE_LIMIT: 1n << 33n, - DELETED: 1n << 34n, - DISABLED_SUSPICIOUS_ACCOUNT: 1n << 35n, - SELF_DELETED: 1n << 36n, - PREMIUM_DISCIRMINATOR: 1n << 37n, - USED_DESKTOP_CLIENT: 1n << 38n, - USED_WEB_CLIENT: 1n << 39n, - USED_MOBILE_CLIENT: 1n << 40n, - DISABLED: 1n << 41n, - // 42 is unknown... - HAS_SESSION_STARTED: 1n << 43n, - QUARANTINED: 1n << 44n, - // 45 and 46 are unknown... - PREMIUM_ELEGIBLE_FOR_UNIQUE_USERNAME: 1n << 47n, - // 48 and 49 are unknown... - COLLABORATOR: 1n << 50n, - RESTRICTED_COLLABORATOR: 1n << 51n, - }; + static FLAGS = { + DISCORD_EMPLOYEE: 1n << 0n, + PARTNERED_SERVER_OWNER: 1n << 1n, + HYPESQUAD_EVENTS: 1n << 2n, + BUGHUNTER_LEVEL_1: 1n << 3n, + MFA_SMS: 1n << 4n, + PREMIUM_PROMO_DISMISSED: 1n << 5n, + HOUSE_BRAVERY: 1n << 6n, + HOUSE_BRILLIANCE: 1n << 7n, + HOUSE_BALANCE: 1n << 8n, + EARLY_SUPPORTER: 1n << 9n, + TEAM_USER: 1n << 10n, + TRUST_AND_SAFETY: 1n << 11n, + SYSTEM: 1n << 12n, + HAS_UNREAD_URGENT_MESSAGES: 1n << 13n, + BUGHUNTER_LEVEL_2: 1n << 14n, + UNDERAGE_DELETED: 1n << 15n, + VERIFIED_BOT: 1n << 16n, + EARLY_VERIFIED_BOT_DEVELOPER: 1n << 17n, + CERTIFIED_MODERATOR: 1n << 18n, + BOT_HTTP_INTERACTIONS: 1n << 19n, + SPAMMER: 1n << 20n, + // @deprecated + DISABLE_PREMIUM: 1n << 21n, + ACTIVE_DEVELOPER: 1n << 22n, + PROVISIONAL_ACCOUNT: 1n << 23n, + // Where did 24-32 go??? + HIGH_GLOBAL_RATE_LIMIT: 1n << 33n, + DELETED: 1n << 34n, + DISABLED_SUSPICIOUS_ACCOUNT: 1n << 35n, + SELF_DELETED: 1n << 36n, + PREMIUM_DISCIRMINATOR: 1n << 37n, + USED_DESKTOP_CLIENT: 1n << 38n, + USED_WEB_CLIENT: 1n << 39n, + USED_MOBILE_CLIENT: 1n << 40n, + DISABLED: 1n << 41n, + // 42 is unknown... + HAS_SESSION_STARTED: 1n << 43n, + QUARANTINED: 1n << 44n, + // 45 and 46 are unknown... + PREMIUM_ELEGIBLE_FOR_UNIQUE_USERNAME: 1n << 47n, + // 48 and 49 are unknown... + COLLABORATOR: 1n << 50n, + RESTRICTED_COLLABORATOR: 1n << 51n, + }; } diff --git a/src/schemas/api/users/UserSettings.ts b/src/schemas/api/users/UserSettings.ts index f91a35d..457d6f4 100644 --- a/src/schemas/api/users/UserSettings.ts +++ b/src/schemas/api/users/UserSettings.ts @@ -19,55 +19,55 @@ export type UserSettingsUpdateSchema = Partial; export interface UserSettingsSchema { - afk_timeout: number; - allow_accessibility_detection: boolean; - animate_emoji: boolean; - animate_stickers: number; - contact_sync_enabled: boolean; - convert_emoticons: boolean; - custom_status: CustomStatus | null; - default_guilds_restricted: boolean; - detect_platform_accounts: boolean; - developer_mode: boolean; - disable_games_tab: boolean; - enable_tts_command: boolean; - explicit_content_filter: number; - friend_discovery_flags: number; - friend_source_flags: FriendSourceFlags; - gateway_connected: boolean; - gif_auto_play: boolean; - guild_folders: GuildFolder[]; // every top guild is displayed as a "folder" - guild_positions: string[]; // guild ids ordered by position - inline_attachment_media: boolean; - inline_embed_media: boolean; - locale: string; // en_US - message_display_compact: boolean; - native_phone_integration_enabled: boolean; - render_embeds: boolean; - render_reactions: boolean; - restricted_guilds: string[]; - show_current_game: boolean; - status: "online" | "offline" | "dnd" | "idle" | "invisible"; - stream_notifications_enabled: boolean; - theme: "dark" | "light"; // dark - timezone_offset: number; // e.g -60 - view_nsfw_guilds: boolean; + afk_timeout: number; + allow_accessibility_detection: boolean; + animate_emoji: boolean; + animate_stickers: number; + contact_sync_enabled: boolean; + convert_emoticons: boolean; + custom_status: CustomStatus | null; + default_guilds_restricted: boolean; + detect_platform_accounts: boolean; + developer_mode: boolean; + disable_games_tab: boolean; + enable_tts_command: boolean; + explicit_content_filter: number; + friend_discovery_flags: number; + friend_source_flags: FriendSourceFlags; + gateway_connected: boolean; + gif_auto_play: boolean; + guild_folders: GuildFolder[]; // every top guild is displayed as a "folder" + guild_positions: string[]; // guild ids ordered by position + inline_attachment_media: boolean; + inline_embed_media: boolean; + locale: string; // en_US + message_display_compact: boolean; + native_phone_integration_enabled: boolean; + render_embeds: boolean; + render_reactions: boolean; + restricted_guilds: string[]; + show_current_game: boolean; + status: "online" | "offline" | "dnd" | "idle" | "invisible"; + stream_notifications_enabled: boolean; + theme: "dark" | "light"; // dark + timezone_offset: number; // e.g -60 + view_nsfw_guilds: boolean; } export interface CustomStatus { - emoji_id?: string; - emoji_name?: string; - expires_at?: number; - text?: string; + emoji_id?: string; + emoji_name?: string; + expires_at?: number; + text?: string; } export interface GuildFolder { - color?: number | null; - guild_ids: string[]; - id?: number | null; - name?: string | null; + color?: number | null; + guild_ids: string[]; + id?: number | null; + name?: string | null; } export interface FriendSourceFlags { - all: boolean; + all: boolean; } diff --git a/src/schemas/gateway/GatewayPayloadSchema.ts b/src/schemas/gateway/GatewayPayloadSchema.ts index 306f2c0..1df6de7 100644 --- a/src/schemas/gateway/GatewayPayloadSchema.ts +++ b/src/schemas/gateway/GatewayPayloadSchema.ts @@ -19,8 +19,8 @@ import { Tuple } from "lambert-server"; export const PayloadSchema = { - op: Number, - $d: new Tuple(Object, Number), // or number for heartbeat sequence - $s: Number, - $t: String, + op: Number, + $d: new Tuple(Object, Number), // or number for heartbeat sequence + $s: Number, + $t: String, }; diff --git a/src/schemas/gateway/IdentifySchema.ts b/src/schemas/gateway/IdentifySchema.ts index f52b8ff..7a80803 100644 --- a/src/schemas/gateway/IdentifySchema.ts +++ b/src/schemas/gateway/IdentifySchema.ts @@ -21,124 +21,124 @@ import { ActivitySchema } from "@spacebar/schemas"; export const IdentifySchema = { - token: String, - $intents: BigInt, // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt - $properties: Object, - // { - // // discord uses $ in the property key for bots, so we need to double prefix it, because instanceOf treats $ (prefix) as a optional key - // $os: String, - // $os_arch: String, - // $browser: String, - // $device: String, - // $$os: String, - // $$browser: String, - // $$device: String, - // $browser_user_agent: String, - // $browser_version: String, - // $os_version: String, - // $referrer: String, - // $$referrer: String, - // $referring_domain: String, - // $$referring_domain: String, - // $referrer_current: String, - // $referring_domain_current: String, - // $release_channel: String, - // $client_build_number: Number, - // $client_event_source: String, - // $client_version: String, - // $system_locale: String, - // $window_manager: String, - // $distro: String, - // }, - $presence: ActivitySchema, - $compress: Boolean, - $large_threshold: Number, - $shard: [BigInt, BigInt], - $guild_subscriptions: Boolean, - $capabilities: Number, - $client_state: { - $guild_hashes: Object, - $highest_last_message_id: Number, - $read_state_version: Number, - $user_guild_settings_version: Number, - $user_settings_version: undefined, - $useruser_guild_settings_version: undefined, - $private_channels_version: Number, - $guild_versions: Object, - $api_code_version: Number, - $initial_guild_id: String, - }, - $clientState: { - $guildHashes: Object, - $highestLastMessageId: Number, - $readStateVersion: Number, - $useruserGuildSettingsVersion: undefined, - $userGuildSettingsVersion: undefined, - $guildVersions: Object, - $apiCodeVersion: Number, - $initialGuildId: String, - }, - $v: Number, - $version: Number, + token: String, + $intents: BigInt, // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt + $properties: Object, + // { + // // discord uses $ in the property key for bots, so we need to double prefix it, because instanceOf treats $ (prefix) as a optional key + // $os: String, + // $os_arch: String, + // $browser: String, + // $device: String, + // $$os: String, + // $$browser: String, + // $$device: String, + // $browser_user_agent: String, + // $browser_version: String, + // $os_version: String, + // $referrer: String, + // $$referrer: String, + // $referring_domain: String, + // $$referring_domain: String, + // $referrer_current: String, + // $referring_domain_current: String, + // $release_channel: String, + // $client_build_number: Number, + // $client_event_source: String, + // $client_version: String, + // $system_locale: String, + // $window_manager: String, + // $distro: String, + // }, + $presence: ActivitySchema, + $compress: Boolean, + $large_threshold: Number, + $shard: [BigInt, BigInt], + $guild_subscriptions: Boolean, + $capabilities: Number, + $client_state: { + $guild_hashes: Object, + $highest_last_message_id: Number, + $read_state_version: Number, + $user_guild_settings_version: Number, + $user_settings_version: undefined, + $useruser_guild_settings_version: undefined, + $private_channels_version: Number, + $guild_versions: Object, + $api_code_version: Number, + $initial_guild_id: String, + }, + $clientState: { + $guildHashes: Object, + $highestLastMessageId: Number, + $readStateVersion: Number, + $useruserGuildSettingsVersion: undefined, + $userGuildSettingsVersion: undefined, + $guildVersions: Object, + $apiCodeVersion: Number, + $initialGuildId: String, + }, + $v: Number, + $version: Number, }; export interface IdentifySchema { - token: string; - properties: { - // bruh discord really uses $ in the property key, so we need to double prefix it, because instanceOf treats $ (prefix) as a optional key - os?: string; - os_atch?: string; - browser?: string; - device?: string; - $os?: string; - $browser?: string; - $device?: string; - browser_user_agent?: string; - browser_version?: string; - os_version?: string; - referrer?: string; - referring_domain?: string; - referrer_current?: string; - referring_domain_current?: string; - release_channel?: "stable" | "dev" | "ptb" | "canary"; - client_build_number?: number; - client_event_source?: string; - client_version?: string; - system_locale?: string; - }; - intents?: bigint; // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt - presence?: ActivitySchema; - compress?: boolean; - large_threshold?: number; - largeThreshold?: number; - /** - * @minItems 2 - * @maxItems 2 - */ - shard?: bigint[]; // puyo: changed from [bigint, bigint] because it breaks openapi - guild_subscriptions?: boolean; - capabilities?: number; - client_state?: { - guild_hashes?: unknown; - highest_last_message_id?: number; - read_state_version?: number; - user_guild_settings_version?: number; - user_settings_version?: number; - useruser_guild_settings_version?: number; - private_channels_version?: number; - guild_versions?: unknown; - api_code_version?: number; - initial_guild_id?: string; - }; - clientState?: { - guildHashes?: unknown; - highestLastMessageId?: number; - readStateVersion?: number; - userGuildSettingsVersion?: number; - useruserGuildSettingsVersion?: number; - guildVersions?: unknown; - apiCodeVersion?: number; - initialGuildId?: string; - }; - v?: number; + token: string; + properties: { + // bruh discord really uses $ in the property key, so we need to double prefix it, because instanceOf treats $ (prefix) as a optional key + os?: string; + os_atch?: string; + browser?: string; + device?: string; + $os?: string; + $browser?: string; + $device?: string; + browser_user_agent?: string; + browser_version?: string; + os_version?: string; + referrer?: string; + referring_domain?: string; + referrer_current?: string; + referring_domain_current?: string; + release_channel?: "stable" | "dev" | "ptb" | "canary"; + client_build_number?: number; + client_event_source?: string; + client_version?: string; + system_locale?: string; + }; + intents?: bigint; // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt + presence?: ActivitySchema; + compress?: boolean; + large_threshold?: number; + largeThreshold?: number; + /** + * @minItems 2 + * @maxItems 2 + */ + shard?: bigint[]; // puyo: changed from [bigint, bigint] because it breaks openapi + guild_subscriptions?: boolean; + capabilities?: number; + client_state?: { + guild_hashes?: unknown; + highest_last_message_id?: number; + read_state_version?: number; + user_guild_settings_version?: number; + user_settings_version?: number; + useruser_guild_settings_version?: number; + private_channels_version?: number; + guild_versions?: unknown; + api_code_version?: number; + initial_guild_id?: string; + }; + clientState?: { + guildHashes?: unknown; + highestLastMessageId?: number; + readStateVersion?: number; + userGuildSettingsVersion?: number; + useruserGuildSettingsVersion?: number; + guildVersions?: unknown; + apiCodeVersion?: number; + initialGuildId?: string; + }; + v?: number; } diff --git a/src/schemas/gateway/LazyRequestSchema.ts b/src/schemas/gateway/LazyRequestSchema.ts index ca07d37..fcefe68 100644 --- a/src/schemas/gateway/LazyRequestSchema.ts +++ b/src/schemas/gateway/LazyRequestSchema.ts @@ -17,30 +17,30 @@ */ export interface LazyRequestSchema { - guild_id: string; - channels?: { - /** - * @items.type integer - * @minItems 2 - * @maxItems 2 - */ - [key: string]: number[][]; // puyo: changed from [number, number] because it breaks openapi - }; - activities?: boolean; - threads?: boolean; - typing?: true; - members?: string[]; - member_updates?: boolean; - thread_member_lists?: unknown[]; + guild_id: string; + channels?: { + /** + * @items.type integer + * @minItems 2 + * @maxItems 2 + */ + [key: string]: number[][]; // puyo: changed from [number, number] because it breaks openapi + }; + activities?: boolean; + threads?: boolean; + typing?: true; + members?: string[]; + member_updates?: boolean; + thread_member_lists?: unknown[]; } export const LazyRequestSchema = { - guild_id: String, - $activities: Boolean, - $channels: Object, - $typing: Boolean, - $threads: Boolean, - $members: [] as string[], - $member_updates: Boolean, - $thread_member_lists: [] as unknown[], + guild_id: String, + $activities: Boolean, + $channels: Object, + $typing: Boolean, + $threads: Boolean, + $members: [] as string[], + $member_updates: Boolean, + $thread_member_lists: [] as unknown[], }; diff --git a/src/schemas/gateway/StreamCreateSchema.ts b/src/schemas/gateway/StreamCreateSchema.ts index bb65079..3e97724 100644 --- a/src/schemas/gateway/StreamCreateSchema.ts +++ b/src/schemas/gateway/StreamCreateSchema.ts @@ -1,13 +1,13 @@ export interface StreamCreateSchema { - type: "guild" | "call"; - channel_id: string; - guild_id?: string; - preferred_region?: string; + type: "guild" | "call"; + channel_id: string; + guild_id?: string; + preferred_region?: string; } export const StreamCreateSchema = { - type: String, - channel_id: String, - $guild_id: String, - $preferred_region: String, + type: String, + channel_id: String, + $guild_id: String, + $preferred_region: String, }; diff --git a/src/schemas/gateway/StreamDeleteSchema.ts b/src/schemas/gateway/StreamDeleteSchema.ts index 0e2aff7..6543444 100644 --- a/src/schemas/gateway/StreamDeleteSchema.ts +++ b/src/schemas/gateway/StreamDeleteSchema.ts @@ -1,7 +1,7 @@ export interface StreamDeleteSchema { - stream_key: string; + stream_key: string; } export const StreamDeleteSchema = { - stream_key: String, + stream_key: String, }; diff --git a/src/schemas/gateway/StreamWatchSchema.ts b/src/schemas/gateway/StreamWatchSchema.ts index 263bb11..14fdd02 100644 --- a/src/schemas/gateway/StreamWatchSchema.ts +++ b/src/schemas/gateway/StreamWatchSchema.ts @@ -1,7 +1,7 @@ export interface StreamWatchSchema { - stream_key: string; + stream_key: string; } export const StreamWatchSchema = { - stream_key: String, + stream_key: String, }; diff --git a/src/schemas/responses/APIErrorResponse.ts b/src/schemas/responses/APIErrorResponse.ts index 547ee58..6297d5b 100644 --- a/src/schemas/responses/APIErrorResponse.ts +++ b/src/schemas/responses/APIErrorResponse.ts @@ -17,14 +17,14 @@ */ export interface APIErrorResponse { - code: number; - message: string; - errors: { - [key: string]: { - _errors: { - message: string; - code: string; - }[]; - }; - }; + code: number; + message: string; + errors: { + [key: string]: { + _errors: { + message: string; + code: string; + }[]; + }; + }; } diff --git a/src/schemas/responses/AccountStandingResponse.ts b/src/schemas/responses/AccountStandingResponse.ts index ea345be..bbb8cd8 100644 --- a/src/schemas/responses/AccountStandingResponse.ts +++ b/src/schemas/responses/AccountStandingResponse.ts @@ -19,141 +19,141 @@ import { Attachment } from "../../util/entities"; export enum AccountStandingState { - ALL_GOOD = 100, - LIMITED = 200, - VERY_LIMITED = 300, - AT_RISK = 400, - SUSPENDED = 500, + ALL_GOOD = 100, + LIMITED = 200, + VERY_LIMITED = 300, + AT_RISK = 400, + SUSPENDED = 500, } export enum AppealEligibility { - DSA_ELIGIBLE = 1, - IN_APP_ELIGIBLE = 2, - AGE_VERIFY_ELIGIBLE = 3, + DSA_ELIGIBLE = 1, + IN_APP_ELIGIBLE = 2, + AGE_VERIFY_ELIGIBLE = 3, } export enum ClassificationType { - UNKNOWN = 1, - UNSOLICITED_PORNOGRAPHY = 100, - NONCONSENSUAL_PORNOGRAPHY = 200, - GLORIFYING_VIOLENCE = 210, - HATE_SPEECH = 220, - CRACKED_ACCOUNTS = 230, - ILLICIT_GOODS = 240, - SOCIAL_ENGINEERING = 250, - CHILD_SAFETY = 280, - HARRASMENT_AND_BULLYING = 290, - HARRASMENT_AND_BULLYING_2 = 310, - HATEFUL_CONDUCT = 320, - HARRASMENT_AND_BULLYING_3 = 390, - CHILD_SAFETY_2 = 600, - CHILD_SAFETY_3 = 650, - IMPERSONATION = 711, - BAN_EVASION = 720, - MALICIOUS_CONDUCT = 3010, - SPAM = 3030, - NONCONSENSUAL_ADULT_CONTENT = 4000, - FRAUD = 4010, - DOXXING_GUILD_OWNER = 4130, - COPYRIGHT_INFRINGEMENT_GUILD_OWNER = 4140, - CHILD_SAFETY_4 = 5010, - CHILD_SELF_ENDANGERMENT = 5090, - DOXXING_GUILD_MEMBER = 5305, - UNDERAGE = 5411, - COPYRIGHT_INFRINGEMENT_GUILD_MEMBER = 5440, - COPYRIGHT_INFRINGEMENT_3 = 5485, + UNKNOWN = 1, + UNSOLICITED_PORNOGRAPHY = 100, + NONCONSENSUAL_PORNOGRAPHY = 200, + GLORIFYING_VIOLENCE = 210, + HATE_SPEECH = 220, + CRACKED_ACCOUNTS = 230, + ILLICIT_GOODS = 240, + SOCIAL_ENGINEERING = 250, + CHILD_SAFETY = 280, + HARRASMENT_AND_BULLYING = 290, + HARRASMENT_AND_BULLYING_2 = 310, + HATEFUL_CONDUCT = 320, + HARRASMENT_AND_BULLYING_3 = 390, + CHILD_SAFETY_2 = 600, + CHILD_SAFETY_3 = 650, + IMPERSONATION = 711, + BAN_EVASION = 720, + MALICIOUS_CONDUCT = 3010, + SPAM = 3030, + NONCONSENSUAL_ADULT_CONTENT = 4000, + FRAUD = 4010, + DOXXING_GUILD_OWNER = 4130, + COPYRIGHT_INFRINGEMENT_GUILD_OWNER = 4140, + CHILD_SAFETY_4 = 5010, + CHILD_SELF_ENDANGERMENT = 5090, + DOXXING_GUILD_MEMBER = 5305, + UNDERAGE = 5411, + COPYRIGHT_INFRINGEMENT_GUILD_MEMBER = 5440, + COPYRIGHT_INFRINGEMENT_3 = 5485, } export enum AppealIngestionType { - WEBFORM = 0, - AGE_VERIFY = 1, - IN_APP = 2, + WEBFORM = 0, + AGE_VERIFY = 1, + IN_APP = 2, } export enum ClassificationActionType { - BAN = 0, - TEMP_BAN = 1, - GLOBAL_QUARANTINE = 2, - REQUIRE_VERIFICATION = 3, - USER_WARNING = 4, - USER_SPAMMER = 5, - CHANNEL_SPAM = 6, - MESSAGE_SPAM = 7, - DISABLE_SUSPICIOUS_ACTIVITY = 8, - LIMITED_ACCESS = 9, - CHANNEL_SCHEDULE_DELETE = 10, - MESSAGE_CONTENT_REMOVAL = 11, - GUILD_DISABLE_INVITE = 12, - USER_CONTENT_REMOVAL = 13, - USER_USERNAME_MANGLED = 14, - GUILD_LIMITED_ACCESS = 15, - USER_MESSAGE_REMOVAL = 16, - GUILD_DELETE = 20, - USER_PROFILE_MANGLED = 22, + BAN = 0, + TEMP_BAN = 1, + GLOBAL_QUARANTINE = 2, + REQUIRE_VERIFICATION = 3, + USER_WARNING = 4, + USER_SPAMMER = 5, + CHANNEL_SPAM = 6, + MESSAGE_SPAM = 7, + DISABLE_SUSPICIOUS_ACTIVITY = 8, + LIMITED_ACCESS = 9, + CHANNEL_SCHEDULE_DELETE = 10, + MESSAGE_CONTENT_REMOVAL = 11, + GUILD_DISABLE_INVITE = 12, + USER_CONTENT_REMOVAL = 13, + USER_USERNAME_MANGLED = 14, + GUILD_LIMITED_ACCESS = 15, + USER_MESSAGE_REMOVAL = 16, + GUILD_DELETE = 20, + USER_PROFILE_MANGLED = 22, } export interface ClassificationAction { - id: string; - action_type: ClassificationActionType; - descriptions: string[]; + id: string; + action_type: ClassificationActionType; + descriptions: string[]; } export enum AppealStatusValue { - REVIEW_PENDING = 1, - CLASSIFICATION_UPHELD = 2, - CLASSIFICATION_INVALIDATED = 3, + REVIEW_PENDING = 1, + CLASSIFICATION_UPHELD = 2, + CLASSIFICATION_INVALIDATED = 3, } export interface AppealStatus { - status: AppealStatusValue; + status: AppealStatusValue; } // why is this just a reduced message? export interface FlaggedContent { - type: "message"; - id: string; - content: string; - attachments: Attachment[]; + type: "message"; + id: string; + content: string; + attachments: Attachment[]; } export interface Classification { - id: string; - classification_type: ClassificationType; - description: string; - explainer_link: string; - actions: ClassificationAction[]; - max_expiration_time: string; // ISO 8601 timestamp - flagged_content: unknown[]; // TODO - appeal_status: AppealStatus; - is_coppa: boolean; - is_spam: boolean; - appeal_ingestion_type: AppealIngestionType; + id: string; + classification_type: ClassificationType; + description: string; + explainer_link: string; + actions: ClassificationAction[]; + max_expiration_time: string; // ISO 8601 timestamp + flagged_content: unknown[]; // TODO + appeal_status: AppealStatus; + is_coppa: boolean; + is_spam: boolean; + appeal_ingestion_type: AppealIngestionType; } export enum GuildMemberType { - OWNER = 1, - MEMBER = 2, + OWNER = 1, + MEMBER = 2, } export interface GuildMetadata { - name: string; - icon?: string; - member_type: GuildMemberType; + name: string; + icon?: string; + member_type: GuildMemberType; } export interface GuildClassification extends Classification { - guild_metadata: GuildMetadata; + guild_metadata: GuildMetadata; } export interface AccountStandingResponse { - classifications: Classification[]; - guild_classifications: GuildClassification[]; - account_standing: { - state: AccountStandingState; - }; - is_dsa_eligible: boolean; - username: string; - discriminator: string; // Not sure if this is even valid, we don't have any examples of pre-pomelo users - is_appeal_eligible: boolean; - appeal_eligibility: AppealEligibility[]; + classifications: Classification[]; + guild_classifications: GuildClassification[]; + account_standing: { + state: AccountStandingState; + }; + is_dsa_eligible: boolean; + username: string; + discriminator: string; // Not sure if this is even valid, we don't have any examples of pre-pomelo users + is_appeal_eligible: boolean; + appeal_eligibility: AppealEligibility[]; } diff --git a/src/schemas/responses/BackupCodesChallengeResponse.ts b/src/schemas/responses/BackupCodesChallengeResponse.ts index b587845..ab6e412 100644 --- a/src/schemas/responses/BackupCodesChallengeResponse.ts +++ b/src/schemas/responses/BackupCodesChallengeResponse.ts @@ -17,6 +17,6 @@ */ export interface BackupCodesChallengeResponse { - nonce: string; - regenerate_nonce: string; + nonce: string; + regenerate_nonce: string; } diff --git a/src/schemas/responses/CaptchaRequiredResponse.ts b/src/schemas/responses/CaptchaRequiredResponse.ts index a58e605..0538a71 100644 --- a/src/schemas/responses/CaptchaRequiredResponse.ts +++ b/src/schemas/responses/CaptchaRequiredResponse.ts @@ -17,7 +17,7 @@ */ export interface CaptchaRequiredResponse { - captcha_key: string; - captcha_sitekey: string; - captcha_service: string; + captcha_key: string; + captcha_sitekey: string; + captcha_service: string; } diff --git a/src/schemas/responses/CollectiblesCategoriesResponse.ts b/src/schemas/responses/CollectiblesCategoriesResponse.ts index 97ade06..2b93ffd 100644 --- a/src/schemas/responses/CollectiblesCategoriesResponse.ts +++ b/src/schemas/responses/CollectiblesCategoriesResponse.ts @@ -22,99 +22,99 @@ export type CollectiblesCategoriesResponse = CollectiblesCategoryItem[]; export interface CollectiblesCategoryStyle { - background_colors: number[]; - button_colors: number[]; - confetti_colors: number[]; + background_colors: number[]; + button_colors: number[]; + confetti_colors: number[]; } export interface CollectiblesCategoryItem { - sku_id: string; - name: string; - summary: string; - store_listing_id: string; - banner: string; - unpublished_at: string | null; - styles: CollectiblesCategoryStyle; - logo: string; - hero_ranking: string[] | null; - mobile_bg: string | null; - pdp_bg: string | null; - success_modal_bg: string | null; - mobile_banner: string | null; - featured_block: string | null; - hero_banner: string | null; - wide_banner: string | null; - hero_logo: string | null; - products: CollectiblesCategoryProductItem[]; - banner_asset?: StaticAnimatedAsset; - hero_banner_asset?: StaticAnimatedAsset; + sku_id: string; + name: string; + summary: string; + store_listing_id: string; + banner: string; + unpublished_at: string | null; + styles: CollectiblesCategoryStyle; + logo: string; + hero_ranking: string[] | null; + mobile_bg: string | null; + pdp_bg: string | null; + success_modal_bg: string | null; + mobile_banner: string | null; + featured_block: string | null; + hero_banner: string | null; + wide_banner: string | null; + hero_logo: string | null; + products: CollectiblesCategoryProductItem[]; + banner_asset?: StaticAnimatedAsset; + hero_banner_asset?: StaticAnimatedAsset; } export interface StaticAnimatedAsset { - // CDN URLs - animated: string | null; - static: string; + // CDN URLs + animated: string | null; + static: string; } export interface CollectiblesCategoryProductItem { - sku_id: string; - name: string; - summary: string; - store_listing_id: string; - banner: string; - unpublished_at: string | null; - styles: CollectiblesCategoryStyle; - prices: PriceMap; - items: ProductItem[]; - type: number; - premium_type: number; - category_sku_id: string; - google_sku_ids: StringStringDictionary; - variants?: ProductItemVariant[]; + sku_id: string; + name: string; + summary: string; + store_listing_id: string; + banner: string; + unpublished_at: string | null; + styles: CollectiblesCategoryStyle; + prices: PriceMap; + items: ProductItem[]; + type: number; + premium_type: number; + category_sku_id: string; + google_sku_ids: StringStringDictionary; + variants?: ProductItemVariant[]; } export interface ProductItemVariant { - sku_id: string; - name: string; - name_localizations: null; - summary: string; - summary_localizations: null; - store_listing_id: string; - banner?: string; - unpublished_at?: string | null; - styles?: CollectiblesCategoryStyle; - prices: PriceMap; - items: ProductItem[]; - type: number; - premium_type: number; - category_sku_id: string; - google_sku_ids?: StringStringDictionary; - base_variant_sku_id: string; - base_variant_name: string; - variant_label: string; - variant_value: string; // hex string + sku_id: string; + name: string; + name_localizations: null; + summary: string; + summary_localizations: null; + store_listing_id: string; + banner?: string; + unpublished_at?: string | null; + styles?: CollectiblesCategoryStyle; + prices: PriceMap; + items: ProductItem[]; + type: number; + premium_type: number; + category_sku_id: string; + google_sku_ids?: StringStringDictionary; + base_variant_sku_id: string; + base_variant_name: string; + variant_label: string; + variant_value: string; // hex string } export interface ProductItem { - type: number; - id: string; - sku_id: string; - asset?: string; - label?: string; - palette?: string; + type: number; + id: string; + sku_id: string; + asset?: string; + label?: string; + palette?: string; } export type PriceMap = { - [key: string]: { country_prices: CountryPrice }; + [key: string]: { country_prices: CountryPrice }; }; export interface CountryPrice { - country_code: string; - prices: PriceEntry[]; + country_code: string; + prices: PriceEntry[]; } export interface PriceEntry { - amount: number; - currency: string; - exponent: number; + amount: number; + currency: string; + exponent: number; } diff --git a/src/schemas/responses/CollectiblesMarketingResponse.ts b/src/schemas/responses/CollectiblesMarketingResponse.ts index 5fda976..bc38cf3 100644 --- a/src/schemas/responses/CollectiblesMarketingResponse.ts +++ b/src/schemas/responses/CollectiblesMarketingResponse.ts @@ -17,44 +17,44 @@ */ export class CollectiblesMarketingResponse { - marketings: { - [key: string]: CollectiblesMarketingItem; - }; + marketings: { + [key: string]: CollectiblesMarketingItem; + }; } export class CollectiblesMarketingItem { - type: number; - version: number; - title: string; - body: string; + type: number; + version: number; + title: string; + body: string; } export class AvatarDecorationMarketingItem extends CollectiblesMarketingItem { - declare type: 0; - // CDN URL to the avatar decoration - avatar: string; - // Asset IDs - decorations: string[]; - dismissible_content: number; // Is this a generic property? - ref_target_background: RefTargetBackground; + declare type: 0; + // CDN URL to the avatar decoration + avatar: string; + // Asset IDs + decorations: string[]; + dismissible_content: number; // Is this a generic property? + ref_target_background: RefTargetBackground; } export class NameplateMarketingItem extends CollectiblesMarketingItem { - declare type: 2; - asset: string; - popout_asset: string; + declare type: 2; + asset: string; + popout_asset: string; } export class TargetBackgroundReference { - light: string | null; - dark: string | null; + light: string | null; + dark: string | null; } export class TargetBackgroundReferenceInteraction { - resting: TargetBackgroundReference; - hovered: TargetBackgroundReference; + resting: TargetBackgroundReference; + hovered: TargetBackgroundReference; } export class RefTargetBackground { - style: TargetBackgroundReferenceInteraction; - asset: TargetBackgroundReferenceInteraction; + style: TargetBackgroundReferenceInteraction; + asset: TargetBackgroundReferenceInteraction; } diff --git a/src/schemas/responses/CollectiblesShopResponse.ts b/src/schemas/responses/CollectiblesShopResponse.ts index f168776..b739fe0 100644 --- a/src/schemas/responses/CollectiblesShopResponse.ts +++ b/src/schemas/responses/CollectiblesShopResponse.ts @@ -19,48 +19,48 @@ import { CollectiblesCategoryItem, StaticAnimatedAsset } from "./CollectiblesCategoriesResponse"; export interface CollectiblesShopResponse { - shop_blocks: AnyShopBlock[]; - categories: CollectiblesCategoryItem[]; + shop_blocks: AnyShopBlock[]; + categories: CollectiblesCategoryItem[]; } export type AnyShopBlock = ItemRowShopBlock | BundleTileRowShopBlock | ItemCollectionShopBlock; export interface BaseShopBlock { - type: number; + type: number; } export interface ItemRowShopBlock extends BaseShopBlock { - type: 0; - category_sku_id: string; - name: string; - category_store_listing_id: string; - banner_asset: StaticAnimatedAsset; - logo_url: string; - unpublished_at: string | null; - summary: string; - ranked_sku_ids: string[]; + type: 0; + category_sku_id: string; + name: string; + category_store_listing_id: string; + banner_asset: StaticAnimatedAsset; + logo_url: string; + unpublished_at: string | null; + summary: string; + ranked_sku_ids: string[]; } export interface BundleTileRowShopBlock extends BaseShopBlock { - type: 1; - subblocks: ShopBlockSubBlock[]; + type: 1; + subblocks: ShopBlockSubBlock[]; } export interface ShopBlockSubBlock { - type: number; - category_store_listing_id: string; - name: string; - unpublished_at: string | null; - banner_url: string; - body_text: string | null; - banner_text_color: number | null; + type: number; + category_store_listing_id: string; + name: string; + unpublished_at: string | null; + banner_url: string; + body_text: string | null; + banner_text_color: number | null; } export interface ItemCollectionShopBlock extends BaseShopBlock { - type: 2; - ranked_sku_ids: string[]; - sorted_sku_ids: { - recommended: string[] | null; - popular: string[]; - }; + type: 2; + ranked_sku_ids: string[]; + sorted_sku_ids: { + recommended: string[] | null; + popular: string[]; + }; } diff --git a/src/schemas/responses/DiscoverableGuildsResponse.ts b/src/schemas/responses/DiscoverableGuildsResponse.ts index 9785c04..3e22c99 100644 --- a/src/schemas/responses/DiscoverableGuildsResponse.ts +++ b/src/schemas/responses/DiscoverableGuildsResponse.ts @@ -19,8 +19,8 @@ import { Guild } from "../../util/entities"; export interface DiscoverableGuildsResponse { - total: number; - guilds: Guild[]; - offset: number; - limit: number; + total: number; + guilds: Guild[]; + offset: number; + limit: number; } diff --git a/src/schemas/responses/EmailDomainLookupResponse.ts b/src/schemas/responses/EmailDomainLookupResponse.ts index dc7faa6..c3ce521 100644 --- a/src/schemas/responses/EmailDomainLookupResponse.ts +++ b/src/schemas/responses/EmailDomainLookupResponse.ts @@ -16,12 +16,12 @@ along with this program. If not, see . */ export interface HubGuild { - icon: string; - id: string; - name: string; + icon: string; + id: string; + name: string; } export interface EmailDomainLookupResponse { - guilds_info: HubGuild[]; - has_matching_guild: boolean; + guilds_info: HubGuild[]; + has_matching_guild: boolean; } diff --git a/src/schemas/responses/EmailDomainLookupVerifyCodeResponse.ts b/src/schemas/responses/EmailDomainLookupVerifyCodeResponse.ts index 3e29c70..6efeaba 100644 --- a/src/schemas/responses/EmailDomainLookupVerifyCodeResponse.ts +++ b/src/schemas/responses/EmailDomainLookupVerifyCodeResponse.ts @@ -19,6 +19,6 @@ import { Guild } from "../../util/entities"; export interface EmailDomainLookupVerifyCodeResponse { - guild: Guild; - joined: boolean; + guild: Guild; + joined: boolean; } diff --git a/src/schemas/responses/EmojiSourceResponse.ts b/src/schemas/responses/EmojiSourceResponse.ts index 6eeebb3..9397027 100644 --- a/src/schemas/responses/EmojiSourceResponse.ts +++ b/src/schemas/responses/EmojiSourceResponse.ts @@ -19,26 +19,26 @@ import { Emoji } from "@spacebar/util"; export interface EmojiSourceResponse { - type: "GUILD" | "APPLICATION"; - guild?: EmojiGuild | null; - application?: EmojiApplication | null; + type: "GUILD" | "APPLICATION"; + guild?: EmojiGuild | null; + application?: EmojiApplication | null; } // keep in sync with export interface EmojiGuild { - id: string; - name: string; - icon?: string | null; - description?: string | null; - features: string[]; - emojis: Emoji[]; - premium_tier: number; - premium_subscription_count?: number; - approximate_member_count?: number; - approximate_presence_count?: number; + id: string; + name: string; + icon?: string | null; + description?: string | null; + features: string[]; + emojis: Emoji[]; + premium_tier: number; + premium_subscription_count?: number; + approximate_member_count?: number; + approximate_presence_count?: number; } export interface EmojiApplication { - id: string; - name: string; + id: string; + name: string; } diff --git a/src/schemas/responses/GatewayBotResponse.ts b/src/schemas/responses/GatewayBotResponse.ts index c68ec09..1bbe159 100644 --- a/src/schemas/responses/GatewayBotResponse.ts +++ b/src/schemas/responses/GatewayBotResponse.ts @@ -17,12 +17,12 @@ */ export interface GatewayBotResponse { - url: string; - shards: number; - session_start_limit: { - total: number; - remaining: number; - reset_after: number; - max_concurrency: number; - }; + url: string; + shards: number; + session_start_limit: { + total: number; + remaining: number; + reset_after: number; + max_concurrency: number; + }; } diff --git a/src/schemas/responses/GatewayResponse.ts b/src/schemas/responses/GatewayResponse.ts index 5b771ed..7d1c659 100644 --- a/src/schemas/responses/GatewayResponse.ts +++ b/src/schemas/responses/GatewayResponse.ts @@ -17,5 +17,5 @@ */ export interface GatewayResponse { - url: string; + url: string; } diff --git a/src/schemas/responses/GenerateRegistrationTokensResponse.ts b/src/schemas/responses/GenerateRegistrationTokensResponse.ts index 4931eb5..b347e16 100644 --- a/src/schemas/responses/GenerateRegistrationTokensResponse.ts +++ b/src/schemas/responses/GenerateRegistrationTokensResponse.ts @@ -17,5 +17,5 @@ */ export interface GenerateRegistrationTokensResponse { - tokens: string[]; + tokens: string[]; } diff --git a/src/schemas/responses/GuildBansResponse.ts b/src/schemas/responses/GuildBansResponse.ts index 91011d5..5e3c3ed 100644 --- a/src/schemas/responses/GuildBansResponse.ts +++ b/src/schemas/responses/GuildBansResponse.ts @@ -17,12 +17,12 @@ */ export interface GuildBansResponse { - reason: string | null; - user: { - username: string; - discriminator: string; - id: string; - avatar: string | null; - public_flags: number; - }; + reason: string | null; + user: { + username: string; + discriminator: string; + id: string; + avatar: string | null; + public_flags: number; + }; } diff --git a/src/schemas/responses/GuildCreateResponse.ts b/src/schemas/responses/GuildCreateResponse.ts index afbf73c..faf7008 100644 --- a/src/schemas/responses/GuildCreateResponse.ts +++ b/src/schemas/responses/GuildCreateResponse.ts @@ -20,24 +20,24 @@ import { GuildUpdateSchema } from "@spacebar/schemas"; export interface GuildCreateResponse extends Omit { - id: string; - name: string; - primary_category_id: string | undefined; - large: boolean | undefined; - max_members: number | undefined; - max_presences: number | undefined; - max_video_channel_users: number | undefined; - member_count: number | undefined; - presence_count: number | undefined; - template_id: string | undefined; - mfa_level: number | undefined; - owner_id: string | undefined; - premium_subscription_count: number | undefined; - premium_tier: number | undefined; - welcome_screen: GuildWelcomeScreen; - widget_channel_id: string | undefined; - widget_enabled: boolean; - nsfw_level: number | undefined; - nsfw: boolean; - parent: string | undefined; + id: string; + name: string; + primary_category_id: string | undefined; + large: boolean | undefined; + max_members: number | undefined; + max_presences: number | undefined; + max_video_channel_users: number | undefined; + member_count: number | undefined; + presence_count: number | undefined; + template_id: string | undefined; + mfa_level: number | undefined; + owner_id: string | undefined; + premium_subscription_count: number | undefined; + premium_tier: number | undefined; + welcome_screen: GuildWelcomeScreen; + widget_channel_id: string | undefined; + widget_enabled: boolean; + nsfw_level: number | undefined; + nsfw: boolean; + parent: string | undefined; } diff --git a/src/schemas/responses/GuildDiscoveryRequirements.ts b/src/schemas/responses/GuildDiscoveryRequirements.ts index 675cdc6..3a5fbbd 100644 --- a/src/schemas/responses/GuildDiscoveryRequirements.ts +++ b/src/schemas/responses/GuildDiscoveryRequirements.ts @@ -17,25 +17,25 @@ */ export interface GuildDiscoveryRequirementsResponse { - guild_id: string; - safe_environment: boolean; - healthy: boolean; - health_score_pending: boolean; - size: boolean; - nsfw_properties: unknown; - protected: boolean; - sufficient: boolean; - sufficient_without_grace_period: boolean; - valid_rules_channel: boolean; - retention_healthy: boolean; - engagement_healthy: boolean; - age: boolean; - minimum_age: number; - health_score: { - avg_nonnew_participators: number; - avg_nonnew_communicators: number; - num_intentful_joiners: number; - perc_ret_w1_intentful: number; - }; - minimum_size: number; + guild_id: string; + safe_environment: boolean; + healthy: boolean; + health_score_pending: boolean; + size: boolean; + nsfw_properties: unknown; + protected: boolean; + sufficient: boolean; + sufficient_without_grace_period: boolean; + valid_rules_channel: boolean; + retention_healthy: boolean; + engagement_healthy: boolean; + age: boolean; + minimum_age: number; + health_score: { + avg_nonnew_participators: number; + avg_nonnew_communicators: number; + num_intentful_joiners: number; + perc_ret_w1_intentful: number; + }; + minimum_size: number; } diff --git a/src/schemas/responses/GuildMessagesSearchResponse.ts b/src/schemas/responses/GuildMessagesSearchResponse.ts index cf59786..6777ad8 100644 --- a/src/schemas/responses/GuildMessagesSearchResponse.ts +++ b/src/schemas/responses/GuildMessagesSearchResponse.ts @@ -20,27 +20,27 @@ import { ActionRowComponent, Embed, MessageType, Poll, PublicUser } from "@spacebar/schemas"; export interface GuildMessagesSearchMessage { - id: string; - type: MessageType; - content?: string; - channel_id: string; - author: PublicUser; - attachments: Attachment[]; - embeds: Embed[]; - mentions: PublicUser[]; - mention_roles: Role[]; - pinned: boolean; - mention_everyone?: boolean; - tts: boolean; - timestamp: string; - edited_timestamp: string | null; - flags: number; - components: ActionRowComponent[]; - poll: Poll; - hit: true; + id: string; + type: MessageType; + content?: string; + channel_id: string; + author: PublicUser; + attachments: Attachment[]; + embeds: Embed[]; + mentions: PublicUser[]; + mention_roles: Role[]; + pinned: boolean; + mention_everyone?: boolean; + tts: boolean; + timestamp: string; + edited_timestamp: string | null; + flags: number; + components: ActionRowComponent[]; + poll: Poll; + hit: true; } export interface GuildMessagesSearchResponse { - messages: GuildMessagesSearchMessage[]; - total_results: number; + messages: GuildMessagesSearchMessage[]; + total_results: number; } diff --git a/src/schemas/responses/GuildPruneResponse.ts b/src/schemas/responses/GuildPruneResponse.ts index 2a24ef6..ef40a0e 100644 --- a/src/schemas/responses/GuildPruneResponse.ts +++ b/src/schemas/responses/GuildPruneResponse.ts @@ -17,9 +17,9 @@ */ export interface GuildPruneResponse { - pruned: number; + pruned: number; } export interface GuildPurgeResponse { - purged: number; + purged: number; } diff --git a/src/schemas/responses/GuildRecommendationsResponse.ts b/src/schemas/responses/GuildRecommendationsResponse.ts index 2f7bb77..04f9d1e 100644 --- a/src/schemas/responses/GuildRecommendationsResponse.ts +++ b/src/schemas/responses/GuildRecommendationsResponse.ts @@ -19,6 +19,6 @@ import { Guild } from "../../util/entities"; export interface GuildRecommendationsResponse { - recommended_guilds: Guild[]; - load_id: string; + recommended_guilds: Guild[]; + load_id: string; } diff --git a/src/schemas/responses/GuildVanityUrl.ts b/src/schemas/responses/GuildVanityUrl.ts index c96049f..45bb7dd 100644 --- a/src/schemas/responses/GuildVanityUrl.ts +++ b/src/schemas/responses/GuildVanityUrl.ts @@ -17,16 +17,16 @@ */ export interface GuildVanityUrl { - code: string; - uses: number; + code: string; + uses: number; } export interface GuildVanityUrlNoInvite { - code: null; + code: null; } export type GuildVanityUrlResponse = GuildVanityUrl | GuildVanityUrl[] | GuildVanityUrlNoInvite; export interface GuildVanityUrlCreateResponse { - code: string; + code: string; } diff --git a/src/schemas/responses/GuildVoiceRegionsResponse.ts b/src/schemas/responses/GuildVoiceRegionsResponse.ts index 63a77f8..ab38c3b 100644 --- a/src/schemas/responses/GuildVoiceRegionsResponse.ts +++ b/src/schemas/responses/GuildVoiceRegionsResponse.ts @@ -17,9 +17,9 @@ */ export interface GuildVoiceRegion { - id: string; - name: string; - custom: boolean; - deprecated: boolean; - optimal: boolean; + id: string; + name: string; + custom: boolean; + deprecated: boolean; + optimal: boolean; } diff --git a/src/schemas/responses/GuildWidgetJsonResponse.ts b/src/schemas/responses/GuildWidgetJsonResponse.ts index bd69230..a1c99dd 100644 --- a/src/schemas/responses/GuildWidgetJsonResponse.ts +++ b/src/schemas/responses/GuildWidgetJsonResponse.ts @@ -19,21 +19,21 @@ import { ClientStatus } from "@spacebar/util"; export interface GuildWidgetJsonResponse { - id: string; - name: string; - instant_invite: string; - channels: { - id: string; - name: string; - position: number; - }[]; - members: { - id: string; - username: string; - discriminator: string; - avatar: string | null; - status: ClientStatus; - avatar_url: string; - }[]; - presence_count: number; + id: string; + name: string; + instant_invite: string; + channels: { + id: string; + name: string; + position: number; + }[]; + members: { + id: string; + username: string; + discriminator: string; + avatar: string | null; + status: ClientStatus; + avatar_url: string; + }[]; + presence_count: number; } diff --git a/src/schemas/responses/GuildWidgetSettingsResponse.ts b/src/schemas/responses/GuildWidgetSettingsResponse.ts index 6ffc771..957aaff 100644 --- a/src/schemas/responses/GuildWidgetSettingsResponse.ts +++ b/src/schemas/responses/GuildWidgetSettingsResponse.ts @@ -19,6 +19,6 @@ import { Snowflake } from "@spacebar/util"; export interface GuildWidgetSettingsResponse { - enabled: boolean; - channel_id: Snowflake | null; + enabled: boolean; + channel_id: Snowflake | null; } diff --git a/src/schemas/responses/HubDirectoryEntriesResponse.ts b/src/schemas/responses/HubDirectoryEntriesResponse.ts index 9efa90c..677199d 100644 --- a/src/schemas/responses/HubDirectoryEntriesResponse.ts +++ b/src/schemas/responses/HubDirectoryEntriesResponse.ts @@ -19,13 +19,13 @@ import { Guild } from "../../util/entities"; export interface HubDirectoryEntry { - author_id: string; - created_at: string; - description: string; - directory_channel_id: string; - guild: Guild; - primary_category_id: number; - type: number; // TODO: not exactly sure what this is, channel type? + author_id: string; + created_at: string; + description: string; + directory_channel_id: string; + guild: Guild; + primary_category_id: number; + type: number; // TODO: not exactly sure what this is, channel type? } export type HubDirectoryEntriesResponse = HubDirectoryEntry[]; diff --git a/src/schemas/responses/HubWaitlistSignupResponse.ts b/src/schemas/responses/HubWaitlistSignupResponse.ts index bd11df2..5555f04 100644 --- a/src/schemas/responses/HubWaitlistSignupResponse.ts +++ b/src/schemas/responses/HubWaitlistSignupResponse.ts @@ -17,8 +17,8 @@ */ export interface HubWaitlistSignupResponse { - email: string; - email_domain: string; - school: string; - user_id: string; + email: string; + email_domain: string; + school: string; + user_id: string; } diff --git a/src/schemas/responses/InstanceDomainsResponse.ts b/src/schemas/responses/InstanceDomainsResponse.ts index d77d445..c9d87e3 100644 --- a/src/schemas/responses/InstanceDomainsResponse.ts +++ b/src/schemas/responses/InstanceDomainsResponse.ts @@ -17,10 +17,10 @@ */ export interface InstanceDomainsResponse { - admin?: string; - api: string; - apiEndpoint: string; - cdn: string; - gateway: string; - defaultApiVersion: string; + admin?: string; + api: string; + apiEndpoint: string; + cdn: string; + gateway: string; + defaultApiVersion: string; } diff --git a/src/schemas/responses/InstancePingResponse.ts b/src/schemas/responses/InstancePingResponse.ts index 4ff3950..fe26419 100644 --- a/src/schemas/responses/InstancePingResponse.ts +++ b/src/schemas/responses/InstancePingResponse.ts @@ -17,15 +17,15 @@ */ export interface InstancePingResponse { - ping: "pong!"; - instance: { - id: string; - name: string; - description: string | null; - image: string | null; - correspondenceEmail: string | null; - correspondenceUserID: string | null; - frontPage: string | null; - tosPage: string | null; - }; + ping: "pong!"; + instance: { + id: string; + name: string; + description: string | null; + image: string | null; + correspondenceEmail: string | null; + correspondenceUserID: string | null; + frontPage: string | null; + tosPage: string | null; + }; } diff --git a/src/schemas/responses/InstanceStatsResponse.ts b/src/schemas/responses/InstanceStatsResponse.ts index 60541f0..0a970fc 100644 --- a/src/schemas/responses/InstanceStatsResponse.ts +++ b/src/schemas/responses/InstanceStatsResponse.ts @@ -17,10 +17,10 @@ */ export interface InstanceStatsResponse { - counts: { - user: number; - guild: number; - message: number; - members: number; - }; + counts: { + user: number; + guild: number; + message: number; + members: number; + }; } diff --git a/src/schemas/responses/LocationMetadataResponse.ts b/src/schemas/responses/LocationMetadataResponse.ts index 2a7ca9b..6eedc2a 100644 --- a/src/schemas/responses/LocationMetadataResponse.ts +++ b/src/schemas/responses/LocationMetadataResponse.ts @@ -17,7 +17,7 @@ */ export interface LocationMetadataResponse { - consent_required: boolean; - country_code: string; - promotional_email_opt_in: { required: true; pre_checked: false }; + consent_required: boolean; + country_code: string; + promotional_email_opt_in: { required: true; pre_checked: false }; } diff --git a/src/schemas/responses/MemberJoinGuildResponse.ts b/src/schemas/responses/MemberJoinGuildResponse.ts index adef59c..1f9bc8c 100644 --- a/src/schemas/responses/MemberJoinGuildResponse.ts +++ b/src/schemas/responses/MemberJoinGuildResponse.ts @@ -20,8 +20,8 @@ import { GuildCreateResponse } from "@spacebar/schemas"; export interface MemberJoinGuildResponse { - guild: GuildCreateResponse; - emojis: Emoji[]; - roles: Role[]; - stickers: Sticker[]; + guild: GuildCreateResponse; + emojis: Emoji[]; + roles: Role[]; + stickers: Sticker[]; } diff --git a/src/schemas/responses/OAuthAuthorizeResponse.ts b/src/schemas/responses/OAuthAuthorizeResponse.ts index ba81f94..764f040 100644 --- a/src/schemas/responses/OAuthAuthorizeResponse.ts +++ b/src/schemas/responses/OAuthAuthorizeResponse.ts @@ -17,5 +17,5 @@ */ export interface OAuthAuthorizeResponse { - location: string; + location: string; } diff --git a/src/schemas/responses/RefreshUrlsResponse.ts b/src/schemas/responses/RefreshUrlsResponse.ts index b08efaa..01767af 100644 --- a/src/schemas/responses/RefreshUrlsResponse.ts +++ b/src/schemas/responses/RefreshUrlsResponse.ts @@ -17,10 +17,10 @@ */ export interface RefreshedUrl { - original: string; - refreshed: string; + original: string; + refreshed: string; } export interface RefreshUrlsResponse { - refreshed_urls: RefreshedUrl[]; + refreshed_urls: RefreshedUrl[]; } diff --git a/src/schemas/responses/SettingsProtoUpdateResponse.ts b/src/schemas/responses/SettingsProtoUpdateResponse.ts index 42f8621..079f36c 100644 --- a/src/schemas/responses/SettingsProtoUpdateResponse.ts +++ b/src/schemas/responses/SettingsProtoUpdateResponse.ts @@ -19,19 +19,19 @@ import { JsonValue } from "@protobuf-ts/runtime"; export interface SettingsProtoResponse { - settings: string; + settings: string; } export interface SettingsProtoUpdateResponse extends SettingsProtoResponse { - out_of_date?: boolean; + out_of_date?: boolean; } export interface SettingsProtoJsonResponse { - settings: JsonValue; + settings: JsonValue; } export interface SettingsProtoUpdateJsonResponse extends SettingsProtoJsonResponse { - out_of_date?: boolean; + out_of_date?: boolean; } // TODO: these dont work with schemas validation diff --git a/src/schemas/responses/Tenor.ts b/src/schemas/responses/Tenor.ts index 5af229e..cd11b6e 100644 --- a/src/schemas/responses/Tenor.ts +++ b/src/schemas/responses/Tenor.ts @@ -17,74 +17,74 @@ */ export enum TenorMediaTypes { - gif, - mediumgif, - tinygif, - nanogif, - mp4, - loopedmp4, - tinymp4, - nanomp4, - webm, - tinywebm, - nanowebm, + gif, + mediumgif, + tinygif, + nanogif, + mp4, + loopedmp4, + tinymp4, + nanomp4, + webm, + tinywebm, + nanowebm, } export type TenorMedia = { - preview: string; - url: string; - dims: number[]; - size: number; + preview: string; + url: string; + dims: number[]; + size: number; }; export type TenorGif = { - created: number; - hasaudio: boolean; - id: string; - media: { [type in keyof typeof TenorMediaTypes]: TenorMedia }[]; - tags: string[]; - title: string; - itemurl: string; - hascaption: boolean; - url: string; + created: number; + hasaudio: boolean; + id: string; + media: { [type in keyof typeof TenorMediaTypes]: TenorMedia }[]; + tags: string[]; + title: string; + itemurl: string; + hascaption: boolean; + url: string; }; export type TenorCategory = { - searchterm: string; - path: string; - image: string; - name: string; + searchterm: string; + path: string; + image: string; + name: string; }; export type TenorCategoriesResults = { - tags: TenorCategory[]; + tags: TenorCategory[]; }; export type TenorTrendingResults = { - next: string; - results: TenorGif[]; - locale: string; + next: string; + results: TenorGif[]; + locale: string; }; export type TenorSearchResults = { - next: string; - results: TenorGif[]; + next: string; + results: TenorGif[]; }; export interface TenorGifResponse { - id: string; - title: string; - url: string; - src: string; - gif_src: string; - width: number; - height: number; - preview: string; + id: string; + title: string; + url: string; + src: string; + gif_src: string; + width: number; + height: number; + preview: string; } export interface TenorTrendingResponse { - categories: TenorCategoriesResults; - gifs: TenorGifResponse[]; + categories: TenorCategoriesResults; + gifs: TenorGifResponse[]; } export type TenorGifsResponse = TenorGifResponse[]; diff --git a/src/schemas/responses/TokenResponse.ts b/src/schemas/responses/TokenResponse.ts index c0f1346..0af06fd 100644 --- a/src/schemas/responses/TokenResponse.ts +++ b/src/schemas/responses/TokenResponse.ts @@ -19,15 +19,15 @@ import { BackupCode, UserSettings } from "../../util/entities"; export interface TokenResponse { - token: string; - settings: UserSettings; + token: string; + settings: UserSettings; } export interface TokenOnlyResponse { - token: string; + token: string; } export interface TokenWithBackupCodesResponse { - token: string; - backup_codes: BackupCode[]; + token: string; + backup_codes: BackupCode[]; } diff --git a/src/schemas/responses/TypedResponses.ts b/src/schemas/responses/TypedResponses.ts index 8938201..13956ec 100644 --- a/src/schemas/responses/TypedResponses.ts +++ b/src/schemas/responses/TypedResponses.ts @@ -36,7 +36,7 @@ export type APIBackupCodeArray = BackupCode[]; export interface UserUpdateResponse extends APIPrivateUser { - newToken?: string; + newToken?: string; } export type ApplicationDetectableResponse = unknown[]; @@ -67,7 +67,7 @@ export type APIPublicMember = PublicMember; export interface APIGuildWithJoinedAt extends GuildCreateResponse { - joined_at: string; + joined_at: string; } export type APIRoleArray = Role[]; @@ -83,8 +83,8 @@ export type APIStickerPackArray = StickerPack[]; export type APIConnectionsConfiguration = Record< - string, - { - enabled: boolean; - } + string, + { + enabled: boolean; + } >; diff --git a/src/schemas/responses/UpdatesResponse.ts b/src/schemas/responses/UpdatesResponse.ts index d8770b4..15a9ed1 100644 --- a/src/schemas/responses/UpdatesResponse.ts +++ b/src/schemas/responses/UpdatesResponse.ts @@ -17,8 +17,8 @@ */ export interface UpdatesResponse { - name: string; - pub_date: string; - url: string; - notes: string | null; + name: string; + pub_date: string; + url: string; + notes: string | null; } diff --git a/src/schemas/responses/UploadAttachmentResponseSchema.ts b/src/schemas/responses/UploadAttachmentResponseSchema.ts index 7d32ead..ce0c66c 100644 --- a/src/schemas/responses/UploadAttachmentResponseSchema.ts +++ b/src/schemas/responses/UploadAttachmentResponseSchema.ts @@ -17,12 +17,12 @@ */ export interface UploadAttachmentResponseSchema { - attachments: UploadAttachmentResponse[]; + attachments: UploadAttachmentResponse[]; } export interface UploadAttachmentResponse { - id?: string; - upload_url: string; - upload_filename: string; - original_content_type?: string; + id?: string; + upload_url: string; + upload_filename: string; + original_content_type?: string; } diff --git a/src/schemas/responses/UserNoteResponse.ts b/src/schemas/responses/UserNoteResponse.ts index 0bde99a..0311179 100644 --- a/src/schemas/responses/UserNoteResponse.ts +++ b/src/schemas/responses/UserNoteResponse.ts @@ -17,7 +17,7 @@ */ export interface UserNoteResponse { - note: string; - note_user_id: string; - user_id: string; + note: string; + note_user_id: string; + user_id: string; } diff --git a/src/schemas/responses/UserProfileResponse.ts b/src/schemas/responses/UserProfileResponse.ts index ee1eee6..656e139 100644 --- a/src/schemas/responses/UserProfileResponse.ts +++ b/src/schemas/responses/UserProfileResponse.ts @@ -20,26 +20,26 @@ import { PublicConnectedAccount, PublicMember, PublicUser } from "@spacebar/schemas"; export type MutualGuild = { - id: string; - nick?: string; + id: string; + nick?: string; }; export type PublicMemberProfile = Pick & { - accent_color: null; // TODO + accent_color: null; // TODO }; export type UserProfile = Pick; export interface UserProfileResponse { - user: PublicUser; - connected_accounts: PublicConnectedAccount; - premium_guild_since?: Date; - premium_since?: Date; - mutual_guilds: MutualGuild[]; - premium_type: number; - profile_themes_experiment_bucket: number; - user_profile: UserProfile; - guild_member?: PublicMember; - guild_member_profile?: PublicMemberProfile; - badges: Badge[]; + user: PublicUser; + connected_accounts: PublicConnectedAccount; + premium_guild_since?: Date; + premium_since?: Date; + mutual_guilds: MutualGuild[]; + premium_type: number; + profile_themes_experiment_bucket: number; + user_profile: UserProfile; + guild_member?: PublicMember; + guild_member_profile?: PublicMemberProfile; + badges: Badge[]; } diff --git a/src/schemas/responses/UserRelationshipsResponse.ts b/src/schemas/responses/UserRelationshipsResponse.ts index 0f12054..92ad30a 100644 --- a/src/schemas/responses/UserRelationshipsResponse.ts +++ b/src/schemas/responses/UserRelationshipsResponse.ts @@ -19,8 +19,8 @@ import { PublicUser, RelationshipType } from "@spacebar/schemas"; export interface UserRelationshipsResponse { - id: string; - type: RelationshipType; - nickname: null; - user: PublicUser; + id: string; + type: RelationshipType; + nickname: null; + user: PublicUser; } diff --git a/src/schemas/responses/WebAuthnCreateResponse.ts b/src/schemas/responses/WebAuthnCreateResponse.ts index 57386e6..b87ff1d 100644 --- a/src/schemas/responses/WebAuthnCreateResponse.ts +++ b/src/schemas/responses/WebAuthnCreateResponse.ts @@ -16,6 +16,6 @@ along with this program. If not, see . */ export interface WebAuthnCreateResponse { - name: string; - id: string; + name: string; + id: string; } diff --git a/src/schemas/responses/WebhookCreateResponse.ts b/src/schemas/responses/WebhookCreateResponse.ts index 6f1cd24..1246c74 100644 --- a/src/schemas/responses/WebhookCreateResponse.ts +++ b/src/schemas/responses/WebhookCreateResponse.ts @@ -19,6 +19,6 @@ import { User, Webhook } from "../../util/entities"; export interface WebhookCreateResponse { - user: User; - hook: Webhook; + user: User; + hook: Webhook; } diff --git a/src/schemas/uncategorised/AckBulkSchema.ts b/src/schemas/uncategorised/AckBulkSchema.ts index 5604c2f..d5f160a 100644 --- a/src/schemas/uncategorised/AckBulkSchema.ts +++ b/src/schemas/uncategorised/AckBulkSchema.ts @@ -17,9 +17,9 @@ */ export interface AckBulkSchema { - read_states: { - channel_id: string; - message_id: string; - read_state_type: number; // WHat is this? - }[]; + read_states: { + channel_id: string; + message_id: string; + read_state_type: number; // WHat is this? + }[]; } diff --git a/src/schemas/uncategorised/ActivitySchema.ts b/src/schemas/uncategorised/ActivitySchema.ts index f168f96..e54948f 100644 --- a/src/schemas/uncategorised/ActivitySchema.ts +++ b/src/schemas/uncategorised/ActivitySchema.ts @@ -19,62 +19,62 @@ import { Activity, Status } from "@spacebar/util"; export const ActivitySchema = { - $afk: Boolean, - status: String, - $activities: [ - { - name: String, - type: Number, - $url: String, - $created_at: Date, - $timestamps: { - $start: Number, - $end: Number, - }, - $application_id: String, - $details: String, - $state: String, - $emoji: { - $name: String, - $id: String, - $animated: Boolean, - }, - $party: { - $id: String, - $size: [Number, Number], - }, - $assets: { - $large_image: String, - $large_text: String, - $small_image: String, - $small_text: String, - }, - $secrets: { - $join: String, - $spectate: String, - $match: String, - }, - $instance: Boolean, - $flags: String, + $afk: Boolean, + status: String, + $activities: [ + { + name: String, + type: Number, + $url: String, + $created_at: Date, + $timestamps: { + $start: Number, + $end: Number, + }, + $application_id: String, + $details: String, + $state: String, + $emoji: { + $name: String, + $id: String, + $animated: Boolean, + }, + $party: { + $id: String, + $size: [Number, Number], + }, + $assets: { + $large_image: String, + $large_text: String, + $small_image: String, + $small_text: String, + }, + $secrets: { + $join: String, + $spectate: String, + $match: String, + }, + $instance: Boolean, + $flags: String, - // Spotify and other rich presence data - $id: String, - $sync_id: String, - $metadata: { - // spotify - $context_uri: String, - album_id: String, - artist_ids: [String], - }, - $session_id: String, - }, - ], - $since: Number, // unix time (in milliseconds) of when the client went idle, or null if the client is not idle + // Spotify and other rich presence data + $id: String, + $sync_id: String, + $metadata: { + // spotify + $context_uri: String, + album_id: String, + artist_ids: [String], + }, + $session_id: String, + }, + ], + $since: Number, // unix time (in milliseconds) of when the client went idle, or null if the client is not idle }; export interface ActivitySchema { - afk?: boolean; - status: Status; - activities?: Activity[]; - since?: number; // unix time (in milliseconds) of when the client went idle, or null if the client is not idle + afk?: boolean; + status: Status; + activities?: Activity[]; + since?: number; // unix time (in milliseconds) of when the client went idle, or null if the client is not idle } diff --git a/src/schemas/uncategorised/ApplicationAuthorizeSchema.ts b/src/schemas/uncategorised/ApplicationAuthorizeSchema.ts index 04b7b1e..5fd1afd 100644 --- a/src/schemas/uncategorised/ApplicationAuthorizeSchema.ts +++ b/src/schemas/uncategorised/ApplicationAuthorizeSchema.ts @@ -17,13 +17,13 @@ */ export interface ApplicationAuthorizeSchema { - authorize: boolean; - guild_id: string; - permissions: string; - captcha_key?: string; - /** - * @minLength 6 - * @maxLength 6 - */ - code?: string; // 2fa code + authorize: boolean; + guild_id: string; + permissions: string; + captcha_key?: string; + /** + * @minLength 6 + * @maxLength 6 + */ + code?: string; // 2fa code } diff --git a/src/schemas/uncategorised/AutomodRuleSchema.ts b/src/schemas/uncategorised/AutomodRuleSchema.ts index 38e8fae..55625bf 100644 --- a/src/schemas/uncategorised/AutomodRuleSchema.ts +++ b/src/schemas/uncategorised/AutomodRuleSchema.ts @@ -17,37 +17,37 @@ */ export interface AutomodMentionSpamRuleSchema { - mention_total_limit: number; - mention_raid_protection_enabled: boolean; + mention_total_limit: number; + mention_raid_protection_enabled: boolean; } // export interface AutomodSuspectedSpamRuleSchema {} export type AutomodSuspectedSpamRuleSchema = Record; // hack to represent an empty object export interface AutomodCommonlyFlaggedWordsRuleSchema { - allow_list: [string]; - presets: [number]; + allow_list: [string]; + presets: [number]; } export interface AutomodCustomWordsRuleSchema { - allow_list: [string]; - keyword_filter: [string]; - regex_patterns: [string]; + allow_list: [string]; + keyword_filter: [string]; + regex_patterns: [string]; } export interface AutomodRuleSchema { - creator_id: string; - enabled: boolean; - event_type: number; // No idea... - exempt_channels: [string]; - exempt_roles: [string]; - guild_id: string; - name: string; - position: number; - trigger_type: number; //AutomodTriggerTypes - trigger_metadata: AutomodMentionSpamRuleSchema | AutomodSuspectedSpamRuleSchema | AutomodCommonlyFlaggedWordsRuleSchema | AutomodCustomWordsRuleSchema; + creator_id: string; + enabled: boolean; + event_type: number; // No idea... + exempt_channels: [string]; + exempt_roles: [string]; + guild_id: string; + name: string; + position: number; + trigger_type: number; //AutomodTriggerTypes + trigger_metadata: AutomodMentionSpamRuleSchema | AutomodSuspectedSpamRuleSchema | AutomodCommonlyFlaggedWordsRuleSchema | AutomodCustomWordsRuleSchema; } export interface AutomodRuleSchemaWithId extends AutomodRuleSchema { - id: string; + id: string; } diff --git a/src/schemas/uncategorised/BackupCodesChallengeSchema.ts b/src/schemas/uncategorised/BackupCodesChallengeSchema.ts index 25f1b70..4e23f32 100644 --- a/src/schemas/uncategorised/BackupCodesChallengeSchema.ts +++ b/src/schemas/uncategorised/BackupCodesChallengeSchema.ts @@ -17,9 +17,9 @@ */ export interface BackupCodesChallengeSchema { - /** - * @minLength 1 - * @maxLength 72 - */ - password: string; + /** + * @minLength 1 + * @maxLength 72 + */ + password: string; } diff --git a/src/schemas/uncategorised/BanCreateSchema.ts b/src/schemas/uncategorised/BanCreateSchema.ts index 8f349f4..e022173 100644 --- a/src/schemas/uncategorised/BanCreateSchema.ts +++ b/src/schemas/uncategorised/BanCreateSchema.ts @@ -17,7 +17,7 @@ */ export interface BanCreateSchema { - delete_message_seconds?: number; - delete_message_days?: number; - reason?: string; + delete_message_seconds?: number; + delete_message_days?: number; + reason?: string; } diff --git a/src/schemas/uncategorised/BanModeratorSchema.ts b/src/schemas/uncategorised/BanModeratorSchema.ts index cba1a5e..5c42399 100644 --- a/src/schemas/uncategorised/BanModeratorSchema.ts +++ b/src/schemas/uncategorised/BanModeratorSchema.ts @@ -17,9 +17,9 @@ */ export interface BanModeratorSchema { - id: string; - user_id: string; - guild_id: string; - executor_id: string; - reason?: string | undefined; + id: string; + user_id: string; + guild_id: string; + executor_id: string; + reason?: string | undefined; } diff --git a/src/schemas/uncategorised/BanRegistrySchema.ts b/src/schemas/uncategorised/BanRegistrySchema.ts index 4e76870..a94bd50 100644 --- a/src/schemas/uncategorised/BanRegistrySchema.ts +++ b/src/schemas/uncategorised/BanRegistrySchema.ts @@ -17,10 +17,10 @@ */ export interface BanRegistrySchema { - id: string; - user_id: string; - guild_id: string; - executor_id: string; - ip?: string; - reason?: string | undefined; + id: string; + user_id: string; + guild_id: string; + executor_id: string; + ip?: string; + reason?: string | undefined; } diff --git a/src/schemas/uncategorised/BotModifySchema.ts b/src/schemas/uncategorised/BotModifySchema.ts index 7d94da2..2127602 100644 --- a/src/schemas/uncategorised/BotModifySchema.ts +++ b/src/schemas/uncategorised/BotModifySchema.ts @@ -17,6 +17,6 @@ */ export interface BotModifySchema { - avatar?: string; - username?: string; + avatar?: string; + username?: string; } diff --git a/src/schemas/uncategorised/BulkBanSchema.ts b/src/schemas/uncategorised/BulkBanSchema.ts index 48a7bac..11bb5dd 100644 --- a/src/schemas/uncategorised/BulkBanSchema.ts +++ b/src/schemas/uncategorised/BulkBanSchema.ts @@ -17,6 +17,6 @@ */ export interface BulkBanSchema { - user_ids: string[]; - delete_message_seconds?: number; + user_ids: string[]; + delete_message_seconds?: number; } diff --git a/src/schemas/uncategorised/BulkDeleteSchema.ts b/src/schemas/uncategorised/BulkDeleteSchema.ts index ca22f10..2d024c3 100644 --- a/src/schemas/uncategorised/BulkDeleteSchema.ts +++ b/src/schemas/uncategorised/BulkDeleteSchema.ts @@ -17,5 +17,5 @@ */ export interface BulkDeleteSchema { - messages: string[]; + messages: string[]; } diff --git a/src/schemas/uncategorised/ChannelModifySchema.ts b/src/schemas/uncategorised/ChannelModifySchema.ts index dcdecc0..226b9ea 100644 --- a/src/schemas/uncategorised/ChannelModifySchema.ts +++ b/src/schemas/uncategorised/ChannelModifySchema.ts @@ -19,30 +19,30 @@ import { ChannelPermissionOverwriteType, ChannelType } from "@spacebar/schemas"; export interface ChannelModifySchema { - /** - * @maxLength 100 - */ - name?: string; - type?: ChannelType; - topic?: string; - icon?: string | null; - bitrate?: number; - user_limit?: number; - rate_limit_per_user?: number; - position?: number; - permission_overwrites?: { - id: string; - type: ChannelPermissionOverwriteType; - allow: string; - deny: string; - }[]; - parent_id?: string; - id?: string; // is not used (only for guild create) - nsfw?: boolean; - rtc_region?: string; - default_auto_archive_duration?: number; - default_reaction_emoji?: string | null; - flags?: number; - default_thread_rate_limit_per_user?: number; - video_quality_mode?: number; + /** + * @maxLength 100 + */ + name?: string; + type?: ChannelType; + topic?: string; + icon?: string | null; + bitrate?: number; + user_limit?: number; + rate_limit_per_user?: number; + position?: number; + permission_overwrites?: { + id: string; + type: ChannelPermissionOverwriteType; + allow: string; + deny: string; + }[]; + parent_id?: string; + id?: string; // is not used (only for guild create) + nsfw?: boolean; + rtc_region?: string; + default_auto_archive_duration?: number; + default_reaction_emoji?: string | null; + flags?: number; + default_thread_rate_limit_per_user?: number; + video_quality_mode?: number; } diff --git a/src/schemas/uncategorised/ChannelReorderSchema.ts b/src/schemas/uncategorised/ChannelReorderSchema.ts index 657ed42..7d6a7d2 100644 --- a/src/schemas/uncategorised/ChannelReorderSchema.ts +++ b/src/schemas/uncategorised/ChannelReorderSchema.ts @@ -17,8 +17,8 @@ */ export type ChannelReorderSchema = { - id: string; - position?: number; - lock_permissions?: boolean; - parent_id?: null | string; + id: string; + position?: number; + lock_permissions?: boolean; + parent_id?: null | string; }[]; diff --git a/src/schemas/uncategorised/CodesVerificationSchema.ts b/src/schemas/uncategorised/CodesVerificationSchema.ts index 3c95e61..5a56fd2 100644 --- a/src/schemas/uncategorised/CodesVerificationSchema.ts +++ b/src/schemas/uncategorised/CodesVerificationSchema.ts @@ -17,7 +17,7 @@ */ export interface CodesVerificationSchema { - key: string; - nonce: string; - regenerate?: boolean; + key: string; + nonce: string; + regenerate?: boolean; } diff --git a/src/schemas/uncategorised/ConnectedAccountSchema.ts b/src/schemas/uncategorised/ConnectedAccountSchema.ts index 4bf4a3d..c1d24d7 100644 --- a/src/schemas/uncategorised/ConnectedAccountSchema.ts +++ b/src/schemas/uncategorised/ConnectedAccountSchema.ts @@ -19,18 +19,18 @@ import { ConnectedAccountTokenData } from "../../util/interfaces"; export interface ConnectedAccountSchema { - external_id: string; - user_id: string; - token_data?: ConnectedAccountTokenData; - friend_sync?: boolean; - name: string; - revoked?: boolean; - show_activity?: number; - type: string; - verified?: boolean; - visibility?: number; - integrations?: string[]; - metadata_?: unknown; - metadata_visibility?: number; - two_way_link?: boolean; + external_id: string; + user_id: string; + token_data?: ConnectedAccountTokenData; + friend_sync?: boolean; + name: string; + revoked?: boolean; + show_activity?: number; + type: string; + verified?: boolean; + visibility?: number; + integrations?: string[]; + metadata_?: unknown; + metadata_visibility?: number; + two_way_link?: boolean; } diff --git a/src/schemas/uncategorised/ConnectionCallbackSchema.ts b/src/schemas/uncategorised/ConnectionCallbackSchema.ts index b66bfe2..8e5aeee 100644 --- a/src/schemas/uncategorised/ConnectionCallbackSchema.ts +++ b/src/schemas/uncategorised/ConnectionCallbackSchema.ts @@ -17,9 +17,9 @@ */ export interface ConnectionCallbackSchema { - code?: string; - state: string; - insecure: boolean; - friend_sync: boolean; - openid_params?: unknown; // TODO: types + code?: string; + state: string; + insecure: boolean; + friend_sync: boolean; + openid_params?: unknown; // TODO: types } diff --git a/src/schemas/uncategorised/ConnectionUpdateSchema.ts b/src/schemas/uncategorised/ConnectionUpdateSchema.ts index f9f17b0..06d1521 100644 --- a/src/schemas/uncategorised/ConnectionUpdateSchema.ts +++ b/src/schemas/uncategorised/ConnectionUpdateSchema.ts @@ -17,7 +17,7 @@ */ export interface ConnectionUpdateSchema { - visibility?: boolean; - show_activity?: boolean; - metadata_visibility?: boolean; + visibility?: boolean; + show_activity?: boolean; + metadata_visibility?: boolean; } diff --git a/src/schemas/uncategorised/DmChannelCreateSchema.ts b/src/schemas/uncategorised/DmChannelCreateSchema.ts index 510ff9c..3a58f66 100644 --- a/src/schemas/uncategorised/DmChannelCreateSchema.ts +++ b/src/schemas/uncategorised/DmChannelCreateSchema.ts @@ -17,6 +17,6 @@ */ export interface DmChannelCreateSchema { - name?: string; - recipients: string[]; + name?: string; + recipients: string[]; } diff --git a/src/schemas/uncategorised/EmailDomainLookupSchema.ts b/src/schemas/uncategorised/EmailDomainLookupSchema.ts index 43680aa..2e82e40 100644 --- a/src/schemas/uncategorised/EmailDomainLookupSchema.ts +++ b/src/schemas/uncategorised/EmailDomainLookupSchema.ts @@ -17,8 +17,8 @@ */ export interface EmailDomainLookupSchema { - allow_multiple_guilds: boolean; - email: string; - use_verification_code: boolean; - guild_id?: string; + allow_multiple_guilds: boolean; + email: string; + use_verification_code: boolean; + guild_id?: string; } diff --git a/src/schemas/uncategorised/EmailDomainLookupVerifyCodeSchema.ts b/src/schemas/uncategorised/EmailDomainLookupVerifyCodeSchema.ts index 9fe0bd4..d2ec566 100644 --- a/src/schemas/uncategorised/EmailDomainLookupVerifyCodeSchema.ts +++ b/src/schemas/uncategorised/EmailDomainLookupVerifyCodeSchema.ts @@ -17,7 +17,7 @@ */ export interface EmailDomainLookupVerifyCodeSchema { - email: string; - guild_id: string; - code: string; + email: string; + guild_id: string; + code: string; } diff --git a/src/schemas/uncategorised/EmojiCreateSchema.ts b/src/schemas/uncategorised/EmojiCreateSchema.ts index 5eae5a9..dac447b 100644 --- a/src/schemas/uncategorised/EmojiCreateSchema.ts +++ b/src/schemas/uncategorised/EmojiCreateSchema.ts @@ -17,8 +17,8 @@ */ export interface EmojiCreateSchema { - name?: string; - image: string; - require_colons?: boolean | null; - roles?: string[]; + name?: string; + image: string; + require_colons?: boolean | null; + roles?: string[]; } diff --git a/src/schemas/uncategorised/EmojiModifySchema.ts b/src/schemas/uncategorised/EmojiModifySchema.ts index 5d33bf6..f4efcce 100644 --- a/src/schemas/uncategorised/EmojiModifySchema.ts +++ b/src/schemas/uncategorised/EmojiModifySchema.ts @@ -17,6 +17,6 @@ */ export interface EmojiModifySchema { - name?: string; - roles?: string[]; + name?: string; + roles?: string[]; } diff --git a/src/schemas/uncategorised/ForgotPasswordSchema.ts b/src/schemas/uncategorised/ForgotPasswordSchema.ts index 0f9fc92..ba8eebb 100644 --- a/src/schemas/uncategorised/ForgotPasswordSchema.ts +++ b/src/schemas/uncategorised/ForgotPasswordSchema.ts @@ -17,6 +17,6 @@ */ export interface ForgotPasswordSchema { - login: string; - captcha_key?: string; + login: string; + captcha_key?: string; } diff --git a/src/schemas/uncategorised/GreetRequestSchema.ts b/src/schemas/uncategorised/GreetRequestSchema.ts index 2a580f1..03a77bd 100644 --- a/src/schemas/uncategorised/GreetRequestSchema.ts +++ b/src/schemas/uncategorised/GreetRequestSchema.ts @@ -19,12 +19,12 @@ import { AllowedMentions } from "@spacebar/schemas"; export interface GreetRequestSchema { - sticker_ids: string[]; - allowed_mentions?: AllowedMentions; - message_reference?: { - message_id: string; - channel_id?: string; - guild_id?: string; - fail_if_not_exists?: boolean; - }; + sticker_ids: string[]; + allowed_mentions?: AllowedMentions; + message_reference?: { + message_id: string; + channel_id?: string; + guild_id?: string; + fail_if_not_exists?: boolean; + }; } diff --git a/src/schemas/uncategorised/GuildCreateSchema.ts b/src/schemas/uncategorised/GuildCreateSchema.ts index f91623d..3ea0451 100644 --- a/src/schemas/uncategorised/GuildCreateSchema.ts +++ b/src/schemas/uncategorised/GuildCreateSchema.ts @@ -19,15 +19,15 @@ import { ChannelModifySchema } from "@spacebar/schemas"; export interface GuildCreateSchema { - /** - * @maxLength 100 - */ - name?: string; - region?: string; - icon?: string | null; - channels?: ChannelModifySchema[]; - system_channel_id?: string; - rules_channel_id?: string; - guild_template_code?: string; - staff_only?: boolean; + /** + * @maxLength 100 + */ + name?: string; + region?: string; + icon?: string | null; + channels?: ChannelModifySchema[]; + system_channel_id?: string; + rules_channel_id?: string; + guild_template_code?: string; + staff_only?: boolean; } diff --git a/src/schemas/uncategorised/GuildSubscriptionsBulkSchema.ts b/src/schemas/uncategorised/GuildSubscriptionsBulkSchema.ts index f4c8a1a..32be9dd 100644 --- a/src/schemas/uncategorised/GuildSubscriptionsBulkSchema.ts +++ b/src/schemas/uncategorised/GuildSubscriptionsBulkSchema.ts @@ -1,11 +1,11 @@ import { LazyRequestSchema } from "../gateway/LazyRequestSchema"; export interface GuildSubscriptionsBulkSchema { - subscriptions: { [key: string]: GuildSubscriptionSchema }; + subscriptions: { [key: string]: GuildSubscriptionSchema }; } export type GuildSubscriptionSchema = Omit; export const GuildSubscriptionsBulkSchema = { - $subscriptions: Object, + $subscriptions: Object, }; diff --git a/src/schemas/uncategorised/GuildTemplateCreateSchema.ts b/src/schemas/uncategorised/GuildTemplateCreateSchema.ts index 048de6f..3ce9862 100644 --- a/src/schemas/uncategorised/GuildTemplateCreateSchema.ts +++ b/src/schemas/uncategorised/GuildTemplateCreateSchema.ts @@ -17,6 +17,6 @@ */ export interface GuildTemplateCreateSchema { - name: string; - avatar?: string | null; + name: string; + avatar?: string | null; } diff --git a/src/schemas/uncategorised/GuildUpdateSchema.ts b/src/schemas/uncategorised/GuildUpdateSchema.ts index 55ccf44..c34c774 100644 --- a/src/schemas/uncategorised/GuildUpdateSchema.ts +++ b/src/schemas/uncategorised/GuildUpdateSchema.ts @@ -19,19 +19,19 @@ import { GuildCreateSchema } from "@spacebar/schemas"; export interface GuildUpdateSchema extends Omit { - banner?: string | null; - splash?: string | null; - description?: string; - features?: string[]; - verification_level?: number; - default_message_notifications?: number; - system_channel_flags?: number; - explicit_content_filter?: number; - public_updates_channel_id?: string; - afk_timeout?: number; - afk_channel_id?: string; - preferred_locale?: string; - premium_progress_bar_enabled?: boolean; - discovery_splash?: string; - safety_alerts_channel_id?: string | null; + banner?: string | null; + splash?: string | null; + description?: string; + features?: string[]; + verification_level?: number; + default_message_notifications?: number; + system_channel_flags?: number; + explicit_content_filter?: number; + public_updates_channel_id?: string; + afk_timeout?: number; + afk_channel_id?: string; + preferred_locale?: string; + premium_progress_bar_enabled?: boolean; + discovery_splash?: string; + safety_alerts_channel_id?: string | null; } diff --git a/src/schemas/uncategorised/GuildUpdateWelcomeScreenSchema.ts b/src/schemas/uncategorised/GuildUpdateWelcomeScreenSchema.ts index 8046aae..4c74f01 100644 --- a/src/schemas/uncategorised/GuildUpdateWelcomeScreenSchema.ts +++ b/src/schemas/uncategorised/GuildUpdateWelcomeScreenSchema.ts @@ -17,12 +17,12 @@ */ export interface GuildUpdateWelcomeScreenSchema { - welcome_channels?: { - channel_id: string; - description: string; - emoji_id?: string; - emoji_name?: string; - }[]; - enabled?: boolean; - description?: string; + welcome_channels?: { + channel_id: string; + description: string; + emoji_id?: string; + emoji_name?: string; + }[]; + enabled?: boolean; + description?: string; } diff --git a/src/schemas/uncategorised/HubWaitlistSignupSchema.ts b/src/schemas/uncategorised/HubWaitlistSignupSchema.ts index 8114b3d..5f7d7de 100644 --- a/src/schemas/uncategorised/HubWaitlistSignupSchema.ts +++ b/src/schemas/uncategorised/HubWaitlistSignupSchema.ts @@ -17,6 +17,6 @@ */ export interface HubWaitlistSignupSchema { - email: string; - school: string; + email: string; + school: string; } diff --git a/src/schemas/uncategorised/InviteCreateSchema.ts b/src/schemas/uncategorised/InviteCreateSchema.ts index 92333e7..1cc5e6a 100644 --- a/src/schemas/uncategorised/InviteCreateSchema.ts +++ b/src/schemas/uncategorised/InviteCreateSchema.ts @@ -17,14 +17,14 @@ */ export interface InviteCreateSchema { - target_user_id?: string; - target_type?: string; - validate?: string; // ? what is this - max_age?: number; - max_uses?: number; - temporary?: boolean; - unique?: boolean; - target_user?: string; - target_user_type?: number; - flags?: number; + target_user_id?: string; + target_type?: string; + validate?: string; // ? what is this + max_age?: number; + max_uses?: number; + temporary?: boolean; + unique?: boolean; + target_user?: string; + target_user_type?: number; + flags?: number; } diff --git a/src/schemas/uncategorised/LoginResponse.ts b/src/schemas/uncategorised/LoginResponse.ts index 5c1e4f8..4a904fc 100644 --- a/src/schemas/uncategorised/LoginResponse.ts +++ b/src/schemas/uncategorised/LoginResponse.ts @@ -1,14 +1,14 @@ import { TokenResponse } from "../responses"; export interface MFAResponse { - ticket: string; - mfa: true; - sms: false; // TODO - token: null; + ticket: string; + mfa: true; + sms: false; // TODO + token: null; } export interface WebAuthnResponse extends MFAResponse { - webauthn: string; + webauthn: string; } export type LoginResponse = TokenResponse | MFAResponse | WebAuthnResponse; diff --git a/src/schemas/uncategorised/LoginSchema.ts b/src/schemas/uncategorised/LoginSchema.ts index 55b6a02..006d20d 100644 --- a/src/schemas/uncategorised/LoginSchema.ts +++ b/src/schemas/uncategorised/LoginSchema.ts @@ -17,14 +17,14 @@ */ export interface LoginSchema { - login: string; - /** - * @minLength 1 - * @maxLength 72 - */ - password: string; - undelete?: boolean; - captcha_key?: string; - login_source?: string; - gift_code_sku_id?: string; + login: string; + /** + * @minLength 1 + * @maxLength 72 + */ + password: string; + undelete?: boolean; + captcha_key?: string; + login_source?: string; + gift_code_sku_id?: string; } diff --git a/src/schemas/uncategorised/MemberChangeProfileSchema.ts b/src/schemas/uncategorised/MemberChangeProfileSchema.ts index d2d1481..fc690bc 100644 --- a/src/schemas/uncategorised/MemberChangeProfileSchema.ts +++ b/src/schemas/uncategorised/MemberChangeProfileSchema.ts @@ -17,12 +17,12 @@ */ export interface MemberChangeProfileSchema { - banner?: string | null; - nick?: string; - bio?: string; - pronouns?: string; - /** - * @items.type integer - */ - theme_colors?: [number, number]; + banner?: string | null; + nick?: string; + bio?: string; + pronouns?: string; + /** + * @items.type integer + */ + theme_colors?: [number, number]; } diff --git a/src/schemas/uncategorised/MemberChangeSchema.ts b/src/schemas/uncategorised/MemberChangeSchema.ts index 8804eff..2318a5e 100644 --- a/src/schemas/uncategorised/MemberChangeSchema.ts +++ b/src/schemas/uncategorised/MemberChangeSchema.ts @@ -17,9 +17,9 @@ */ export interface MemberChangeSchema { - roles?: string[]; - nick?: string; - avatar?: string | null; - bio?: string; - communication_disabled_until?: string | null; + roles?: string[]; + nick?: string; + avatar?: string | null; + bio?: string; + communication_disabled_until?: string | null; } diff --git a/src/schemas/uncategorised/MemberNickChangeSchema.ts b/src/schemas/uncategorised/MemberNickChangeSchema.ts index 4589056..50273a3 100644 --- a/src/schemas/uncategorised/MemberNickChangeSchema.ts +++ b/src/schemas/uncategorised/MemberNickChangeSchema.ts @@ -17,5 +17,5 @@ */ export interface MemberNickChangeSchema { - nick: string; + nick: string; } diff --git a/src/schemas/uncategorised/MessageAcknowledgeSchema.ts b/src/schemas/uncategorised/MessageAcknowledgeSchema.ts index 0075fb3..dd6e64b 100644 --- a/src/schemas/uncategorised/MessageAcknowledgeSchema.ts +++ b/src/schemas/uncategorised/MessageAcknowledgeSchema.ts @@ -17,29 +17,29 @@ */ export interface MessageAcknowledgeSchema { - manual?: boolean; - mention_count?: number; - flags?: ReadStateFlags | 0; - last_viewed?: number; - token?: string; + manual?: boolean; + mention_count?: number; + flags?: ReadStateFlags | 0; + last_viewed?: number; + token?: string; } export interface AcknowledgeDeleteSchema { - read_state_type?: ReadStateType; - version?: number; + read_state_type?: ReadStateType; + version?: number; } export enum ReadStateType { - CHANNEL = 0, - GUILD_EVENT = 1, - NOTIFICATION_CENTER = 2, - GUILD_HOME = 3, - GUILD_ONBOARDING_QUESTION = 4, - MESSAGE_REQUESTS = 5, + CHANNEL = 0, + GUILD_EVENT = 1, + NOTIFICATION_CENTER = 2, + GUILD_HOME = 3, + GUILD_ONBOARDING_QUESTION = 4, + MESSAGE_REQUESTS = 5, } export enum ReadStateFlags { - IS_GUILD_CHANNEL = 1 << 0, - IS_THREAD = 1 << 1, - IS_MENTION_LOW_IMPORTANCE = 1 << 2, + IS_GUILD_CHANNEL = 1 << 0, + IS_THREAD = 1 << 1, + IS_MENTION_LOW_IMPORTANCE = 1 << 2, } diff --git a/src/schemas/uncategorised/MessageCreateSchema.ts b/src/schemas/uncategorised/MessageCreateSchema.ts index 4d32ead..bdf2ab2 100644 --- a/src/schemas/uncategorised/MessageCreateSchema.ts +++ b/src/schemas/uncategorised/MessageCreateSchema.ts @@ -20,80 +20,80 @@ import { ActionRowComponent, ApplicationCommandType, Embed, PollAnswer, PollMedia, PublicUser } from "@spacebar/schemas"; export type MessageCreateAttachment = { - id: string; - filename: string; + id: string; + filename: string; }; export type MessageCreateCloudAttachment = { - id?: string; - filename: string; - uploaded_filename: string; - original_content_type?: string; + id?: string; + filename: string; + uploaded_filename: string; + original_content_type?: string; }; export interface MessageCreateSchema { - type?: number; - content?: string; - mobile_network_type?: string; - nonce?: string; - channel_id?: string; - tts?: boolean; - flags?: number; - embeds?: Embed[]; - embed?: Embed; - // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object) - allowed_mentions?: { - parse?: string[]; - roles?: string[]; - users?: string[]; - replied_user?: boolean; - }; - message_reference?: { - message_id: string; - channel_id?: string; - guild_id?: string; - fail_if_not_exists?: boolean; - }; - payload_json?: string; - file?: { filename: string }; - /** + type?: number; + content?: string; + mobile_network_type?: string; + nonce?: string; + channel_id?: string; + tts?: boolean; + flags?: number; + embeds?: Embed[]; + embed?: Embed; + // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object) + allowed_mentions?: { + parse?: string[]; + roles?: string[]; + users?: string[]; + replied_user?: boolean; + }; + message_reference?: { + message_id: string; + channel_id?: string; + guild_id?: string; + fail_if_not_exists?: boolean; + }; + payload_json?: string; + file?: { filename: string }; + /** TODO: we should create an interface for attachments TODO: OpenWAAO<-->attachment-style metadata conversion **/ - attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment)[]; - sticker_ids?: string[] | null; // null check: fixes Discord-Go - components?: ActionRowComponent[] | null; // null check: fixes Discord-Go - // TODO: Fix TypeScript errors in src\api\util\handlers\Message.ts once this is enabled - poll?: PollCreationSchema; - enforce_nonce?: boolean; // For Discord compatibility, it's the default behavior here - applied_tags?: string[]; // Not implemented yet, for webhooks in forums - thread_name?: string; // Not implemented yet, for webhooks - avatar_url?: string; // Not implemented yet, for webhooks - interaction?: MessageInteractionSchema; - interaction_metadata?: MessageInteractionSchema; + attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment)[]; + sticker_ids?: string[] | null; // null check: fixes Discord-Go + components?: ActionRowComponent[] | null; // null check: fixes Discord-Go + // TODO: Fix TypeScript errors in src\api\util\handlers\Message.ts once this is enabled + poll?: PollCreationSchema; + enforce_nonce?: boolean; // For Discord compatibility, it's the default behavior here + applied_tags?: string[]; // Not implemented yet, for webhooks in forums + thread_name?: string; // Not implemented yet, for webhooks + avatar_url?: string; // Not implemented yet, for webhooks + interaction?: MessageInteractionSchema; + interaction_metadata?: MessageInteractionSchema; } // TypeScript complains once this is used above export interface PollCreationSchema { - question: PollMedia; - answers: PollAnswer[]; - duration?: number; - allow_multiselect?: boolean; - layout_type?: number; + question: PollMedia; + answers: PollAnswer[]; + duration?: number; + allow_multiselect?: boolean; + layout_type?: number; } interface MessageInteractionSchema { - id: string; - type: InteractionType; - name: string; - command_type?: ApplicationCommandType; - ephemerality_reason?: number; - user?: PublicUser; // It has to be optional cause LSP gives an errors for some reason - user_id?: string; - authorizing_integration_owners?: object; // It has to be optional cause LSP gives an errors for some reason - original_response_message_id?: Snowflake; - interacted_message_id?: Snowflake; - triggering_interaction_metadata?: MessageInteractionSchema; - target_user?: PublicUser; - target_message_id?: Snowflake; + id: string; + type: InteractionType; + name: string; + command_type?: ApplicationCommandType; + ephemerality_reason?: number; + user?: PublicUser; // It has to be optional cause LSP gives an errors for some reason + user_id?: string; + authorizing_integration_owners?: object; // It has to be optional cause LSP gives an errors for some reason + original_response_message_id?: Snowflake; + interacted_message_id?: Snowflake; + triggering_interaction_metadata?: MessageInteractionSchema; + target_user?: PublicUser; + target_message_id?: Snowflake; } diff --git a/src/schemas/uncategorised/MfaCodesSchema.ts b/src/schemas/uncategorised/MfaCodesSchema.ts index 1f66f08..bd6ceb4 100644 --- a/src/schemas/uncategorised/MfaCodesSchema.ts +++ b/src/schemas/uncategorised/MfaCodesSchema.ts @@ -17,10 +17,10 @@ */ export interface MfaCodesSchema { - /** - * @minLength 1 - * @maxLength 72 - */ - password: string; - regenerate?: boolean; + /** + * @minLength 1 + * @maxLength 72 + */ + password: string; + regenerate?: boolean; } diff --git a/src/schemas/uncategorised/ModifyGuildStickerSchema.ts b/src/schemas/uncategorised/ModifyGuildStickerSchema.ts index 95eb541..8673265 100644 --- a/src/schemas/uncategorised/ModifyGuildStickerSchema.ts +++ b/src/schemas/uncategorised/ModifyGuildStickerSchema.ts @@ -17,17 +17,17 @@ */ export interface ModifyGuildStickerSchema { - /** - * @minLength 2 - * @maxLength 30 - */ - name: string; - /** - * @maxLength 100 - */ - description?: string; - /** - * @maxLength 200 - */ - tags: string; + /** + * @minLength 2 + * @maxLength 30 + */ + name: string; + /** + * @maxLength 100 + */ + description?: string; + /** + * @maxLength 200 + */ + tags: string; } diff --git a/src/schemas/uncategorised/PasswordResetSchema.ts b/src/schemas/uncategorised/PasswordResetSchema.ts index 15489a9..26e24f9 100644 --- a/src/schemas/uncategorised/PasswordResetSchema.ts +++ b/src/schemas/uncategorised/PasswordResetSchema.ts @@ -17,10 +17,10 @@ */ export interface PasswordResetSchema { - /** - * @minLength 1 - * @maxLength 72 - */ - password: string; - token: string; + /** + * @minLength 1 + * @maxLength 72 + */ + password: string; + token: string; } diff --git a/src/schemas/uncategorised/PreloadMessagesRequestSchema.ts b/src/schemas/uncategorised/PreloadMessagesRequestSchema.ts index 7c5145b..1dc1eec 100644 --- a/src/schemas/uncategorised/PreloadMessagesRequestSchema.ts +++ b/src/schemas/uncategorised/PreloadMessagesRequestSchema.ts @@ -17,5 +17,5 @@ */ export interface PreloadMessagesRequestSchema { - channels: string[]; + channels: string[]; } diff --git a/src/schemas/uncategorised/PruneSchema.ts b/src/schemas/uncategorised/PruneSchema.ts index 88d2f6e..eaf0e7e 100644 --- a/src/schemas/uncategorised/PruneSchema.ts +++ b/src/schemas/uncategorised/PruneSchema.ts @@ -17,8 +17,8 @@ */ export interface PruneSchema { - /** - * @min 0 - */ - days: number; + /** + * @min 0 + */ + days: number; } diff --git a/src/schemas/uncategorised/PurgeSchema.ts b/src/schemas/uncategorised/PurgeSchema.ts index 2e88799..d9a7f68 100644 --- a/src/schemas/uncategorised/PurgeSchema.ts +++ b/src/schemas/uncategorised/PurgeSchema.ts @@ -17,6 +17,6 @@ */ export interface PurgeSchema { - before: string; - after: string; + before: string; + after: string; } diff --git a/src/schemas/uncategorised/RefreshUrlsRequestSchema.ts b/src/schemas/uncategorised/RefreshUrlsRequestSchema.ts index 9c1df54..6268dd1 100644 --- a/src/schemas/uncategorised/RefreshUrlsRequestSchema.ts +++ b/src/schemas/uncategorised/RefreshUrlsRequestSchema.ts @@ -17,5 +17,5 @@ */ export interface RefreshUrlsRequestSchema { - attachment_urls: string[]; + attachment_urls: string[]; } diff --git a/src/schemas/uncategorised/RegisterSchema.ts b/src/schemas/uncategorised/RegisterSchema.ts index cfee0f0..28b7bde 100644 --- a/src/schemas/uncategorised/RegisterSchema.ts +++ b/src/schemas/uncategorised/RegisterSchema.ts @@ -17,32 +17,32 @@ */ export interface RegisterSchema { - /** - * @minLength 2 - */ - username: string; - /** - * @minLength 1 - * @maxLength 72 - */ - password?: string; - consent: boolean; - /** - * @TJS-format email - */ - email?: string; - fingerprint?: string; - invite?: string; - /** - * @TJS-type string - */ - date_of_birth?: Date; // "2000-04-03" - gift_code_sku_id?: string; - captcha_key?: string; + /** + * @minLength 2 + */ + username: string; + /** + * @minLength 1 + * @maxLength 72 + */ + password?: string; + consent: boolean; + /** + * @TJS-format email + */ + email?: string; + fingerprint?: string; + invite?: string; + /** + * @TJS-type string + */ + date_of_birth?: Date; // "2000-04-03" + gift_code_sku_id?: string; + captcha_key?: string; - promotional_email_opt_in?: boolean; + promotional_email_opt_in?: boolean; - // part of pomelo - unique_username_registration?: boolean; - global_name?: string; + // part of pomelo + unique_username_registration?: boolean; + global_name?: string; } diff --git a/src/schemas/uncategorised/RelationshipPostSchema.ts b/src/schemas/uncategorised/RelationshipPostSchema.ts index 066ecfd..5f2d943 100644 --- a/src/schemas/uncategorised/RelationshipPostSchema.ts +++ b/src/schemas/uncategorised/RelationshipPostSchema.ts @@ -17,6 +17,6 @@ */ export interface RelationshipPostSchema { - discriminator: string; - username: string; + discriminator: string; + username: string; } diff --git a/src/schemas/uncategorised/RelationshipPutSchema.ts b/src/schemas/uncategorised/RelationshipPutSchema.ts index 563e46d..e29170e 100644 --- a/src/schemas/uncategorised/RelationshipPutSchema.ts +++ b/src/schemas/uncategorised/RelationshipPutSchema.ts @@ -17,12 +17,12 @@ */ export interface RelationshipPutSchema { - type?: RelationshipType; + type?: RelationshipType; } export enum RelationshipType { - outgoing = 4, - incoming = 3, - blocked = 2, - friends = 1, + outgoing = 4, + incoming = 3, + blocked = 2, + friends = 1, } diff --git a/src/schemas/uncategorised/RequestGuildMembersSchema.ts b/src/schemas/uncategorised/RequestGuildMembersSchema.ts index 9e60d26..925b090 100644 --- a/src/schemas/uncategorised/RequestGuildMembersSchema.ts +++ b/src/schemas/uncategorised/RequestGuildMembersSchema.ts @@ -17,19 +17,19 @@ */ export interface RequestGuildMembersSchema { - guild_id: string | [string]; - query?: string; - limit?: number; - presences?: boolean; - user_ids?: string | string[]; - nonce?: string; + guild_id: string | [string]; + query?: string; + limit?: number; + presences?: boolean; + user_ids?: string | string[]; + nonce?: string; } export const RequestGuildMembersSchema = { - guild_id: "" as string | string[], - $query: String, - $limit: Number, - $presences: Boolean, - $user_ids: [] as string | string[], - $nonce: String, + guild_id: "" as string | string[], + $query: String, + $limit: Number, + $presences: Boolean, + $user_ids: [] as string | string[], + $nonce: String, }; diff --git a/src/schemas/uncategorised/RoleModifySchema.ts b/src/schemas/uncategorised/RoleModifySchema.ts index 19cd604..cd1e5f3 100644 --- a/src/schemas/uncategorised/RoleModifySchema.ts +++ b/src/schemas/uncategorised/RoleModifySchema.ts @@ -17,19 +17,19 @@ */ export interface RoleModifySchema { - name?: string; - permissions?: string; - color?: number; - hoist?: boolean; // whether the role should be displayed separately in the sidebar - mentionable?: boolean; // whether the role should be mentionable - position?: number; - icon?: string; - unicode_emoji?: string; - colors?: - | { - primary_color: number; - secondary_color?: number | null | undefined; // only used for "holographic" and "gradient" styles - tertiary_color?: number | null | undefined; // only used for "holographic" style - } - | undefined; + name?: string; + permissions?: string; + color?: number; + hoist?: boolean; // whether the role should be displayed separately in the sidebar + mentionable?: boolean; // whether the role should be mentionable + position?: number; + icon?: string; + unicode_emoji?: string; + colors?: + | { + primary_color: number; + secondary_color?: number | null | undefined; // only used for "holographic" and "gradient" styles + tertiary_color?: number | null | undefined; // only used for "holographic" style + } + | undefined; } diff --git a/src/schemas/uncategorised/RolePositionUpdateSchema.ts b/src/schemas/uncategorised/RolePositionUpdateSchema.ts index 6bfd7f3..86526e7 100644 --- a/src/schemas/uncategorised/RolePositionUpdateSchema.ts +++ b/src/schemas/uncategorised/RolePositionUpdateSchema.ts @@ -17,6 +17,6 @@ */ export type RolePositionUpdateSchema = { - id: string; - position: number; + id: string; + position: number; }[]; diff --git a/src/schemas/uncategorised/SelectProtocolSchema.ts b/src/schemas/uncategorised/SelectProtocolSchema.ts index d04adf7..02c31b4 100644 --- a/src/schemas/uncategorised/SelectProtocolSchema.ts +++ b/src/schemas/uncategorised/SelectProtocolSchema.ts @@ -17,21 +17,21 @@ */ export interface SelectProtocolSchema { - protocol: "webrtc" | "udp"; - data: - | string - | { - address: string; - port: number; - mode: string; - }; - sdp?: string; - codecs?: { - name: "opus" | "VP8" | "VP9" | "H264"; - type: "audio" | "video"; - priority: number; - payload_type: number; - rtx_payload_type?: number; - }[]; - rtc_connection_id?: string; // uuid + protocol: "webrtc" | "udp"; + data: + | string + | { + address: string; + port: number; + mode: string; + }; + sdp?: string; + codecs?: { + name: "opus" | "VP8" | "VP9" | "H264"; + type: "audio" | "video"; + priority: number; + payload_type: number; + rtx_payload_type?: number; + }[]; + rtc_connection_id?: string; // uuid } diff --git a/src/schemas/uncategorised/SettingsProtoUpdateSchema.ts b/src/schemas/uncategorised/SettingsProtoUpdateSchema.ts index a7e884e..5bf6808 100644 --- a/src/schemas/uncategorised/SettingsProtoUpdateSchema.ts +++ b/src/schemas/uncategorised/SettingsProtoUpdateSchema.ts @@ -19,13 +19,13 @@ import { JsonValue } from "@protobuf-ts/runtime"; export interface SettingsProtoUpdateSchema { - settings: string; - required_data_version?: number; + settings: string; + required_data_version?: number; } export interface SettingsProtoUpdateJsonSchema { - settings: JsonValue; - required_data_version?: number; + settings: JsonValue; + required_data_version?: number; } // TODO: these dont work with schema validation diff --git a/src/schemas/uncategorised/TeamCreateSchema.ts b/src/schemas/uncategorised/TeamCreateSchema.ts index 5903a53..2a2a328 100644 --- a/src/schemas/uncategorised/TeamCreateSchema.ts +++ b/src/schemas/uncategorised/TeamCreateSchema.ts @@ -17,5 +17,5 @@ */ export interface TeamCreateSchema { - name: string; + name: string; } diff --git a/src/schemas/uncategorised/TemplateCreateSchema.ts b/src/schemas/uncategorised/TemplateCreateSchema.ts index 9e562c9..85c1a4e 100644 --- a/src/schemas/uncategorised/TemplateCreateSchema.ts +++ b/src/schemas/uncategorised/TemplateCreateSchema.ts @@ -17,6 +17,6 @@ */ export interface TemplateCreateSchema { - name: string; - description?: string; + name: string; + description?: string; } diff --git a/src/schemas/uncategorised/TemplateModifySchema.ts b/src/schemas/uncategorised/TemplateModifySchema.ts index 49b0237..01b9981 100644 --- a/src/schemas/uncategorised/TemplateModifySchema.ts +++ b/src/schemas/uncategorised/TemplateModifySchema.ts @@ -17,6 +17,6 @@ */ export interface TemplateModifySchema { - name: string; - description?: string; + name: string; + description?: string; } diff --git a/src/schemas/uncategorised/TotpDisableSchema.ts b/src/schemas/uncategorised/TotpDisableSchema.ts index 29706f4..6420381 100644 --- a/src/schemas/uncategorised/TotpDisableSchema.ts +++ b/src/schemas/uncategorised/TotpDisableSchema.ts @@ -17,9 +17,9 @@ */ export interface TotpDisableSchema { - /** - * @minLength 6 - * @maxLength 6 - */ - code: string; + /** + * @minLength 6 + * @maxLength 6 + */ + code: string; } diff --git a/src/schemas/uncategorised/TotpEnableSchema.ts b/src/schemas/uncategorised/TotpEnableSchema.ts index a80e06a..1fbab81 100644 --- a/src/schemas/uncategorised/TotpEnableSchema.ts +++ b/src/schemas/uncategorised/TotpEnableSchema.ts @@ -17,15 +17,15 @@ */ export interface TotpEnableSchema { - /** - * @minLength 1 - * @maxLength 72 - */ - password: string; - /** - * @minLength 6 - * @maxLength 6 - */ - code?: string; - secret?: string; + /** + * @minLength 1 + * @maxLength 72 + */ + password: string; + /** + * @minLength 6 + * @maxLength 6 + */ + code?: string; + secret?: string; } diff --git a/src/schemas/uncategorised/TotpSchema.ts b/src/schemas/uncategorised/TotpSchema.ts index 657b7e7..a7e8294 100644 --- a/src/schemas/uncategorised/TotpSchema.ts +++ b/src/schemas/uncategorised/TotpSchema.ts @@ -17,8 +17,8 @@ */ export interface TotpSchema { - code: string; - ticket: string; - gift_code_sku_id?: string | null; - login_source?: string | null; + code: string; + ticket: string; + gift_code_sku_id?: string | null; + login_source?: string | null; } diff --git a/src/schemas/uncategorised/UploadAttachmentRequestSchema.ts b/src/schemas/uncategorised/UploadAttachmentRequestSchema.ts index 0ad3a7c..b2a905b 100644 --- a/src/schemas/uncategorised/UploadAttachmentRequestSchema.ts +++ b/src/schemas/uncategorised/UploadAttachmentRequestSchema.ts @@ -17,13 +17,13 @@ */ export interface UploadAttachmentRequestSchema { - files: UploadAttachmentRequest[]; + files: UploadAttachmentRequest[]; } export interface UploadAttachmentRequest { - id?: string; - filename: string; - file_size: number; - is_clip?: boolean; - original_content_type?: string; + id?: string; + filename: string; + file_size: number; + is_clip?: boolean; + original_content_type?: string; } diff --git a/src/schemas/uncategorised/UserDeleteSchema.ts b/src/schemas/uncategorised/UserDeleteSchema.ts index 40fb778..fcd69d8 100644 --- a/src/schemas/uncategorised/UserDeleteSchema.ts +++ b/src/schemas/uncategorised/UserDeleteSchema.ts @@ -17,5 +17,5 @@ */ export interface UserDeleteSchema { - user_id: string; + user_id: string; } diff --git a/src/schemas/uncategorised/UserGuildSettingsSchema.ts b/src/schemas/uncategorised/UserGuildSettingsSchema.ts index 20de7e5..4ed3025 100644 --- a/src/schemas/uncategorised/UserGuildSettingsSchema.ts +++ b/src/schemas/uncategorised/UserGuildSettingsSchema.ts @@ -20,7 +20,7 @@ // This sucks. I would use a DeepPartial, my own or typeorms, but they both generate inncorect schema export interface UserGuildSettingsSchema extends Partial> { - channel_overrides?: { - [channel_id: string]: ChannelOverride; - }; + channel_overrides?: { + [channel_id: string]: ChannelOverride; + }; } diff --git a/src/schemas/uncategorised/UserModifySchema.ts b/src/schemas/uncategorised/UserModifySchema.ts index 6d2998e..c7228e7 100644 --- a/src/schemas/uncategorised/UserModifySchema.ts +++ b/src/schemas/uncategorised/UserModifySchema.ts @@ -17,36 +17,36 @@ */ export interface UserModifySchema { - /** - * @minLength 2 - */ - username?: string; - avatar?: string | null; - bio?: string; - accent_color?: number; - banner?: string | null; - /** - * @minLength 1 - * @maxLength 72 - */ - password?: string; - /** - * @minLength 1 - * @maxLength 72 - */ - new_password?: string; - /** - * @minLength 6 - * @maxLength 6 - */ - code?: string; - /** - * @TJS-format email - */ - email?: string; - /** - * @minLength 4 - * @maxLength 4 - */ - discriminator?: string; + /** + * @minLength 2 + */ + username?: string; + avatar?: string | null; + bio?: string; + accent_color?: number; + banner?: string | null; + /** + * @minLength 1 + * @maxLength 72 + */ + password?: string; + /** + * @minLength 1 + * @maxLength 72 + */ + new_password?: string; + /** + * @minLength 6 + * @maxLength 6 + */ + code?: string; + /** + * @TJS-format email + */ + email?: string; + /** + * @minLength 4 + * @maxLength 4 + */ + discriminator?: string; } diff --git a/src/schemas/uncategorised/UserNoteUpdateSchema.ts b/src/schemas/uncategorised/UserNoteUpdateSchema.ts index 0a73127..f543069 100644 --- a/src/schemas/uncategorised/UserNoteUpdateSchema.ts +++ b/src/schemas/uncategorised/UserNoteUpdateSchema.ts @@ -1,3 +1,3 @@ export interface UserNoteUpdateSchema { - note: string; + note: string; } diff --git a/src/schemas/uncategorised/UserProfileModifySchema.ts b/src/schemas/uncategorised/UserProfileModifySchema.ts index 3dea257..5ba1dca 100644 --- a/src/schemas/uncategorised/UserProfileModifySchema.ts +++ b/src/schemas/uncategorised/UserProfileModifySchema.ts @@ -17,12 +17,12 @@ */ export interface UserProfileModifySchema { - bio?: string; - accent_color?: number | null; - banner?: string | null; - pronouns?: string; - /** - * @items.type integer - */ - theme_colors?: [number, number]; + bio?: string; + accent_color?: number | null; + banner?: string | null; + pronouns?: string; + /** + * @items.type integer + */ + theme_colors?: [number, number]; } diff --git a/src/schemas/uncategorised/VanityUrlSchema.ts b/src/schemas/uncategorised/VanityUrlSchema.ts index 12d5abd..2273c0f 100644 --- a/src/schemas/uncategorised/VanityUrlSchema.ts +++ b/src/schemas/uncategorised/VanityUrlSchema.ts @@ -17,9 +17,9 @@ */ export interface VanityUrlSchema { - /** - * @minLength 1 - * @maxLength 20 - */ - code?: string; + /** + * @minLength 1 + * @maxLength 20 + */ + code?: string; } diff --git a/src/schemas/uncategorised/VerifyEmailSchema.ts b/src/schemas/uncategorised/VerifyEmailSchema.ts index ce297c5..818b028 100644 --- a/src/schemas/uncategorised/VerifyEmailSchema.ts +++ b/src/schemas/uncategorised/VerifyEmailSchema.ts @@ -17,6 +17,6 @@ */ export interface VerifyEmailSchema { - captcha_key?: string | null; - token: string; + captcha_key?: string | null; + token: string; } diff --git a/src/schemas/uncategorised/VoiceStateUpdateSchema.ts b/src/schemas/uncategorised/VoiceStateUpdateSchema.ts index fda073e..1d4cee8 100644 --- a/src/schemas/uncategorised/VoiceStateUpdateSchema.ts +++ b/src/schemas/uncategorised/VoiceStateUpdateSchema.ts @@ -18,25 +18,25 @@ //TODO need more testing when community guild and voice stage channel are working export interface VoiceStateUpdateSchema { - guild_id?: string; - channel_id?: string; - self_mute: boolean; - self_deaf: boolean; - self_video?: boolean; - preferred_region?: string; - request_to_speak_timestamp?: Date; - suppress?: boolean; - flags?: number; + guild_id?: string; + channel_id?: string; + self_mute: boolean; + self_deaf: boolean; + self_video?: boolean; + preferred_region?: string; + request_to_speak_timestamp?: Date; + suppress?: boolean; + flags?: number; } export const VoiceStateUpdateSchema = { - $guild_id: String, - $channel_id: String, - self_mute: Boolean, - self_deaf: Boolean, - $self_video: Boolean, //required in docs but bots don't always send it - $preferred_region: String, - $request_to_speak_timestamp: Date, - $suppress: Boolean, - $flags: Number, + $guild_id: String, + $channel_id: String, + self_mute: Boolean, + self_deaf: Boolean, + $self_video: Boolean, //required in docs but bots don't always send it + $preferred_region: String, + $request_to_speak_timestamp: Date, + $suppress: Boolean, + $flags: Number, }; diff --git a/src/schemas/uncategorised/WebAuthnSchema.ts b/src/schemas/uncategorised/WebAuthnSchema.ts index ada20de..8b4db9a 100644 --- a/src/schemas/uncategorised/WebAuthnSchema.ts +++ b/src/schemas/uncategorised/WebAuthnSchema.ts @@ -18,19 +18,19 @@ // FIXME: better naming export interface GenerateWebAuthnCredentialsSchema { - password: string; + password: string; } // FIXME: better naming export interface CreateWebAuthnCredentialSchema { - credential: string; - name: string; - ticket: string; + credential: string; + name: string; + ticket: string; } export type WebAuthnPostSchema = GenerateWebAuthnCredentialsSchema | CreateWebAuthnCredentialSchema; export interface WebAuthnTotpSchema { - code: string; - ticket: string; + code: string; + ticket: string; } diff --git a/src/schemas/uncategorised/WebhookCreateSchema.ts b/src/schemas/uncategorised/WebhookCreateSchema.ts index 7bd0afa..df93f65 100644 --- a/src/schemas/uncategorised/WebhookCreateSchema.ts +++ b/src/schemas/uncategorised/WebhookCreateSchema.ts @@ -17,9 +17,9 @@ */ export interface WebhookCreateSchema { - /** - * @maxLength 80 - */ - name: string; - avatar?: string; + /** + * @maxLength 80 + */ + name: string; + avatar?: string; } diff --git a/src/schemas/uncategorised/WebhookExecuteSchema.ts b/src/schemas/uncategorised/WebhookExecuteSchema.ts index 4957c02..2d0fdbd 100644 --- a/src/schemas/uncategorised/WebhookExecuteSchema.ts +++ b/src/schemas/uncategorised/WebhookExecuteSchema.ts @@ -20,37 +20,37 @@ import { MessageCreateAttachment, PollCreationSchema } from "./MessageCreateSchema"; export interface WebhookExecuteSchema { - content?: string; - username?: string; - avatar_url?: string; - tts?: boolean; - embeds?: Embed[]; - allowed_mentions?: { - parse?: string[]; - roles?: string[]; - users?: string[]; - replied_user?: boolean; - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - components?: any[]; - file?: { filename: string }; - payload_json?: string; - /** + content?: string; + username?: string; + avatar_url?: string; + tts?: boolean; + embeds?: Embed[]; + allowed_mentions?: { + parse?: string[]; + roles?: string[]; + users?: string[]; + replied_user?: boolean; + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + components?: any[]; + file?: { filename: string }; + payload_json?: string; + /** TODO: we should create an interface for attachments TODO: OpenWAAO<-->attachment-style metadata conversion **/ - attachments?: MessageCreateAttachment[]; - flags?: number; - thread_name?: string; - applied_tags?: string[]; - message_reference?: { - message_id: string; - channel_id?: string; - guild_id?: string; - fail_if_not_exists?: boolean; - }; - sticker_ids?: string[]; - nonce?: string; - enforce_nonce?: boolean; // For Discord compatibility, it's the default behavior here - poll?: PollCreationSchema; + attachments?: MessageCreateAttachment[]; + flags?: number; + thread_name?: string; + applied_tags?: string[]; + message_reference?: { + message_id: string; + channel_id?: string; + guild_id?: string; + fail_if_not_exists?: boolean; + }; + sticker_ids?: string[]; + nonce?: string; + enforce_nonce?: boolean; // For Discord compatibility, it's the default behavior here + poll?: PollCreationSchema; } diff --git a/src/schemas/uncategorised/WebhookUpdateSchema.ts b/src/schemas/uncategorised/WebhookUpdateSchema.ts index bc276e4..e3fa4c5 100755 --- a/src/schemas/uncategorised/WebhookUpdateSchema.ts +++ b/src/schemas/uncategorised/WebhookUpdateSchema.ts @@ -17,7 +17,7 @@ */ export interface WebhookUpdateSchema { - name?: string; - avatar?: string; - channel_id?: string; + name?: string; + avatar?: string; + channel_id?: string; } diff --git a/src/schemas/uncategorised/WidgetModifySchema.ts b/src/schemas/uncategorised/WidgetModifySchema.ts index 50794d6..9bb19ee 100644 --- a/src/schemas/uncategorised/WidgetModifySchema.ts +++ b/src/schemas/uncategorised/WidgetModifySchema.ts @@ -17,6 +17,6 @@ */ export interface WidgetModifySchema { - enabled: boolean; // whether the widget is enabled - channel_id: string; // the widget channel id + enabled: boolean; // whether the widget is enabled + channel_id: string; // the widget channel id } diff --git a/src/schemas/webrtc/Region.ts b/src/schemas/webrtc/Region.ts index 1b8171f..57f1602 100644 --- a/src/schemas/webrtc/Region.ts +++ b/src/schemas/webrtc/Region.ts @@ -17,14 +17,14 @@ */ export interface Region { - id: string; - name: string; - endpoint: string; - location?: { - latitude: number; - longitude: number; - }; - vip: boolean; - custom: boolean; - deprecated: boolean; + id: string; + name: string; + endpoint: string; + location?: { + latitude: number; + longitude: number; + }; + vip: boolean; + custom: boolean; + deprecated: boolean; } diff --git a/src/schemas/webrtc/VoiceIdentifySchema.ts b/src/schemas/webrtc/VoiceIdentifySchema.ts index c89aa0b..203020b 100644 --- a/src/schemas/webrtc/VoiceIdentifySchema.ts +++ b/src/schemas/webrtc/VoiceIdentifySchema.ts @@ -17,18 +17,18 @@ */ export interface VoiceIdentifySchema { - server_id: string; - user_id: string; - session_id: string; - channel_id?: string; - token: string; - video?: boolean; - streams?: { - type: "video" | "audio" | "screen"; - rid: string; - quality: number; - }[]; - // Discord keeps changing the property name of this, probably will keep changing until Dave is finalized - max_secure_frames_version?: number; - max_dave_protocol_version?: number; + server_id: string; + user_id: string; + session_id: string; + channel_id?: string; + token: string; + video?: boolean; + streams?: { + type: "video" | "audio" | "screen"; + rid: string; + quality: number; + }[]; + // Discord keeps changing the property name of this, probably will keep changing until Dave is finalized + max_secure_frames_version?: number; + max_dave_protocol_version?: number; } diff --git a/src/schemas/webrtc/VoiceVideoSchema.ts b/src/schemas/webrtc/VoiceVideoSchema.ts index c621431..64f301c 100644 --- a/src/schemas/webrtc/VoiceVideoSchema.ts +++ b/src/schemas/webrtc/VoiceVideoSchema.ts @@ -17,19 +17,19 @@ */ export interface VoiceVideoSchema { - audio_ssrc: number; - video_ssrc: number; - rtx_ssrc?: number; - user_id?: string; - streams?: { - type: "video" | "audio" | "screen"; - rid: string; - ssrc: number; - active: boolean; - quality: number; - rtx_ssrc: number; - max_bitrate: number; - max_framerate: number; - max_resolution: { type: string; width: number; height: number }; - }[]; + audio_ssrc: number; + video_ssrc: number; + rtx_ssrc?: number; + user_id?: string; + streams?: { + type: "video" | "audio" | "screen"; + rid: string; + ssrc: number; + active: boolean; + quality: number; + rtx_ssrc: number; + max_bitrate: number; + max_framerate: number; + max_resolution: { type: string; width: number; height: number }; + }[]; } diff --git a/src/schemas/webrtc/index.ts b/src/schemas/webrtc/index.ts index b25b57d..b4c023a 100644 --- a/src/schemas/webrtc/index.ts +++ b/src/schemas/webrtc/index.ts @@ -18,4 +18,4 @@ export * from "./VoiceIdentifySchema"; export * from "./VoiceVideoSchema"; -export * from "./Region" \ No newline at end of file +export * from "./Region"; diff --git a/src/util/Signing.ts b/src/util/Signing.ts index 17f5d64..26daf80 100644 --- a/src/util/Signing.ts +++ b/src/util/Signing.ts @@ -22,239 +22,239 @@ import * as console from "node:console"; export class NewUrlUserSignatureData { - ip?: string; - userAgent?: string; + ip?: string; + userAgent?: string; - constructor(data: NewUrlUserSignatureData) { - this.ip = data.ip; - this.userAgent = data.userAgent; - } + constructor(data: NewUrlUserSignatureData) { + this.ip = data.ip; + this.userAgent = data.userAgent; + } } export class NewUrlSignatureData extends NewUrlUserSignatureData { - path?: string; - url?: string; + path?: string; + url?: string; - constructor(data: NewUrlSignatureData) { - super(data); - this.path = data.path; - this.url = data.url; - if (!this.path && !this.url) { - throw new Error("Either path or url must be provided for URL signing"); - } - if (this.path && this.url) { - if (process.env["LOG_CDN_SIGNATURES"]) console.warn("[Signing] Both path and url are provided, using path for signing", this, new Error().stack); - } - if (this.url) { - try { - const parsedUrl = new URL(this.url); - this.path = parsedUrl.pathname; - } catch (e) { - throw new Error("Invalid URL provided for signing: " + this.url); - } - } - } + constructor(data: NewUrlSignatureData) { + super(data); + this.path = data.path; + this.url = data.url; + if (!this.path && !this.url) { + throw new Error("Either path or url must be provided for URL signing"); + } + if (this.path && this.url) { + if (process.env["LOG_CDN_SIGNATURES"]) console.warn("[Signing] Both path and url are provided, using path for signing", this, new Error().stack); + } + if (this.url) { + try { + const parsedUrl = new URL(this.url); + this.path = parsedUrl.pathname; + } catch (e) { + throw new Error("Invalid URL provided for signing: " + this.url); + } + } + } } export class UrlSignatureData extends NewUrlSignatureData { - issuedAt: string; - expiresAt: string; + issuedAt: string; + expiresAt: string; - constructor(data: UrlSignatureData) { - super(data); - this.issuedAt = data.issuedAt; - this.expiresAt = data.expiresAt; - } + constructor(data: UrlSignatureData) { + super(data); + this.issuedAt = data.issuedAt; + this.expiresAt = data.expiresAt; + } } export class UrlSignResult { - path: string; - hash: string; - issuedAt: string; - expiresAt: string; + path: string; + hash: string; + issuedAt: string; + expiresAt: string; - constructor(data: Partial) { - for (const key in data) { - // @ts-expect-error TS7053 - We dont care about string indexing a class - this[key] = data[key]; - } - } + constructor(data: Partial) { + for (const key in data) { + // @ts-expect-error TS7053 - We dont care about string indexing a class + this[key] = data[key]; + } + } - applyToUrl(url: URL | string): URL { - if (typeof url === "string") { - url = new URL(url); - } - url.searchParams.set("ex", this.expiresAt); - url.searchParams.set("is", this.issuedAt); - url.searchParams.set("hm", this.hash); - return url; - } + applyToUrl(url: URL | string): URL { + if (typeof url === "string") { + url = new URL(url); + } + url.searchParams.set("ex", this.expiresAt); + url.searchParams.set("is", this.issuedAt); + url.searchParams.set("hm", this.hash); + return url; + } - static fromUrl(url: URL | string): UrlSignResult { - if (typeof url === "string") { - if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Parsing URL from string:", url); - url = new URL(url); - } - if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Parsing URL from URL object:", url.toString()); - const ex = url.searchParams.get("ex"); - const is = url.searchParams.get("is"); - const hm = url.searchParams.get("hm"); + static fromUrl(url: URL | string): UrlSignResult { + if (typeof url === "string") { + if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Parsing URL from string:", url); + url = new URL(url); + } + if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Parsing URL from URL object:", url.toString()); + const ex = url.searchParams.get("ex"); + const is = url.searchParams.get("is"); + const hm = url.searchParams.get("hm"); - if (!ex || !is || !hm) { - throw new Error("Invalid URL signature parameters"); - } + if (!ex || !is || !hm) { + throw new Error("Invalid URL signature parameters"); + } - return new UrlSignResult({ - path: url.pathname, - issuedAt: is, - expiresAt: ex, - hash: hm, - }); - } + return new UrlSignResult({ + path: url.pathname, + issuedAt: is, + expiresAt: ex, + hash: hm, + }); + } } export const getUrlSignature = (data: NewUrlSignatureData): UrlSignResult => { - const { cdnSignatureKey, cdnSignatureDuration } = Config.get().security; + const { cdnSignatureKey, cdnSignatureDuration } = Config.get().security; - // calculate the expiration time - const now = Date.now(); - const issuedAt = now.toString(16); - const expiresAt = (now + ms(cdnSignatureDuration as StringValue)).toString(16); + // calculate the expiration time + const now = Date.now(); + const issuedAt = now.toString(16); + const expiresAt = (now + ms(cdnSignatureDuration as StringValue)).toString(16); - // hash the url with the cdnSignatureKey - return calculateHash( - new UrlSignatureData({ - ...data, - issuedAt, - expiresAt, - }), - ); + // hash the url with the cdnSignatureKey + return calculateHash( + new UrlSignatureData({ + ...data, + issuedAt, + expiresAt, + }), + ); }; function calculateHash(request: UrlSignatureData): UrlSignResult { - const { cdnSignatureKey } = Config.get().security; - const data = createHmac("sha256", cdnSignatureKey as string) - .update(request.path!) - .update(request.issuedAt) - .update(request.expiresAt); + const { cdnSignatureKey } = Config.get().security; + const data = createHmac("sha256", cdnSignatureKey as string) + .update(request.path!) + .update(request.issuedAt) + .update(request.expiresAt); - if (Config.get().security.cdnSignatureIncludeIp) { - if (!request.ip) - console.log( - "[Signing] CDN Signature IP is enabled but we couldn't find the IP field in the request. This may cause issues with signature validation. Please report this to the Spacebar team!", - ); - else { - if (process.env["LOG_CDN_SIGNATURES"]) console.log("[Signing] CDN Signature IP is enabled, adding IP to hash:", request.ip); - data.update(request.ip!); - } - } + if (Config.get().security.cdnSignatureIncludeIp) { + if (!request.ip) + console.log( + "[Signing] CDN Signature IP is enabled but we couldn't find the IP field in the request. This may cause issues with signature validation. Please report this to the Spacebar team!", + ); + else { + if (process.env["LOG_CDN_SIGNATURES"]) console.log("[Signing] CDN Signature IP is enabled, adding IP to hash:", request.ip); + data.update(request.ip!); + } + } - if (Config.get().security.cdnSignatureIncludeUserAgent) { - if (!request.userAgent) - console.log( - "[Signing] CDN Signature User-Agent is enabled but we couldn't find the user-agent header in the request. This may cause issues with signature validation. Please report this to the Spacebar team!", - ); - else { - if (process.env["LOG_CDN_SIGNATURES"]) console.log("[Signing] CDN Signature User-Agent is enabled, adding User-Agent to hash:", request.userAgent); - data.update(request.userAgent!); - } - } + if (Config.get().security.cdnSignatureIncludeUserAgent) { + if (!request.userAgent) + console.log( + "[Signing] CDN Signature User-Agent is enabled but we couldn't find the user-agent header in the request. This may cause issues with signature validation. Please report this to the Spacebar team!", + ); + else { + if (process.env["LOG_CDN_SIGNATURES"]) console.log("[Signing] CDN Signature User-Agent is enabled, adding User-Agent to hash:", request.userAgent); + data.update(request.userAgent!); + } + } - const hash = data.digest("hex"); - const result = new UrlSignResult({ - path: request.path, - issuedAt: request.issuedAt, - expiresAt: request.expiresAt, - hash, - }); - if (process.env["LOG_CDN_SIGNATURES"]) - console.log( - "[Signing]", - { - path: request.path, - validity: request.issuedAt + " .. " + request.expiresAt, - ua: Config.get().security.cdnSignatureIncludeUserAgent ? request.userAgent : "[disabled]", - ip: Config.get().security.cdnSignatureIncludeIp ? request.ip : "[disabled]", - }, - "->", - result, - ); - return result; + const hash = data.digest("hex"); + const result = new UrlSignResult({ + path: request.path, + issuedAt: request.issuedAt, + expiresAt: request.expiresAt, + hash, + }); + if (process.env["LOG_CDN_SIGNATURES"]) + console.log( + "[Signing]", + { + path: request.path, + validity: request.issuedAt + " .. " + request.expiresAt, + ua: Config.get().security.cdnSignatureIncludeUserAgent ? request.userAgent : "[disabled]", + ip: Config.get().security.cdnSignatureIncludeIp ? request.ip : "[disabled]", + }, + "->", + result, + ); + return result; } export const isExpired = (data: UrlSignResult | UrlSignatureData) => { - // convert issued at - const issuedAt = parseInt(data.issuedAt, 16); - const expiresAt = parseInt(data.expiresAt, 16); + // convert issued at + const issuedAt = parseInt(data.issuedAt, 16); + const expiresAt = parseInt(data.expiresAt, 16); - if (Number.isNaN(issuedAt) || Number.isNaN(expiresAt)) { - if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Invalid timestamps in query"); - return true; - } + if (Number.isNaN(issuedAt) || Number.isNaN(expiresAt)) { + if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Invalid timestamps in query"); + return true; + } - const currentTime = Date.now(); + const currentTime = Date.now(); - const isExpired = expiresAt < currentTime; - if (isExpired) { - if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Signature expired"); - return true; - } + const isExpired = expiresAt < currentTime; + if (isExpired) { + if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Signature expired"); + return true; + } - const isValidIssuedAt = issuedAt < currentTime; - if (!isValidIssuedAt) { - if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Signature issued in the future"); - return true; - } + const isValidIssuedAt = issuedAt < currentTime; + if (!isValidIssuedAt) { + if (process.env["LOG_CDN_SIGNATURES"]) console.debug("[Signing] Signature issued in the future"); + return true; + } - return false; + return false; }; export const hasValidSignature = (req: NewUrlUserSignatureData, sig: UrlSignResult) => { - // if the required query parameters are not present, return false - if (!sig.expiresAt || !sig.issuedAt || !sig.hash) { - if (process.env["LOG_CDN_SIGNATURES"]) console.warn("[Signing] Missing required query parameters for signature validation"); - return false; - } + // if the required query parameters are not present, return false + if (!sig.expiresAt || !sig.issuedAt || !sig.hash) { + if (process.env["LOG_CDN_SIGNATURES"]) console.warn("[Signing] Missing required query parameters for signature validation"); + return false; + } - // check if the signature is expired - if (isExpired(sig)) { - if (process.env["LOG_CDN_SIGNATURES"]) console.warn("[Signing] Signature is expired"); - return false; - } + // check if the signature is expired + if (isExpired(sig)) { + if (process.env["LOG_CDN_SIGNATURES"]) console.warn("[Signing] Signature is expired"); + return false; + } - const calcResult = calculateHash( - new UrlSignatureData({ - path: sig.path, - issuedAt: sig.issuedAt, - expiresAt: sig.expiresAt, - ip: req.ip, - userAgent: req.userAgent, - }), - ); - const calcd = calcResult.hash; - const calculated = Buffer.from(calcd); - const received = Buffer.from(sig.hash as string); + const calcResult = calculateHash( + new UrlSignatureData({ + path: sig.path, + issuedAt: sig.issuedAt, + expiresAt: sig.expiresAt, + ip: req.ip, + userAgent: req.userAgent, + }), + ); + const calcd = calcResult.hash; + const calculated = Buffer.from(calcd); + const received = Buffer.from(sig.hash as string); - console.assert(sig.issuedAt == calcResult.issuedAt, "[Signing] Mismatched issuedAt", { - is: sig.issuedAt, - calculated: calcResult.issuedAt, - }); + console.assert(sig.issuedAt == calcResult.issuedAt, "[Signing] Mismatched issuedAt", { + is: sig.issuedAt, + calculated: calcResult.issuedAt, + }); - console.assert(sig.expiresAt == calcResult.expiresAt, "[Signing] Mismatched expiresAt", { - ex: sig.expiresAt, - calculated: calcResult.expiresAt, - }); + console.assert(sig.expiresAt == calcResult.expiresAt, "[Signing] Mismatched expiresAt", { + ex: sig.expiresAt, + calculated: calcResult.expiresAt, + }); - console.assert(calculated.length === received.length, "[Signing] Mismatched hash length", { - calculated: calculated.length, - received: received.length, - }); + console.assert(calculated.length === received.length, "[Signing] Mismatched hash length", { + calculated: calculated.length, + received: received.length, + }); - const isHashValid = calculated.length === received.length && timingSafeEqual(calculated, received); + const isHashValid = calculated.length === received.length && timingSafeEqual(calculated, received); - if (!isHashValid) - if (process.env["LOG_CDN_SIGNATURES"]) - console.warn(`Signature validation for ${sig.path} (is=${sig.issuedAt}, ex=${sig.expiresAt}) failed: calculated: ${calcd}, received: ${sig.hash}`); + if (!isHashValid) + if (process.env["LOG_CDN_SIGNATURES"]) + console.warn(`Signature validation for ${sig.path} (is=${sig.issuedAt}, ex=${sig.expiresAt}) failed: calculated: ${calcd}, received: ${sig.hash}`); - return isHashValid; + return isHashValid; }; diff --git a/src/util/config/Config.ts b/src/util/config/Config.ts index 7c4ee8a..0a56766 100644 --- a/src/util/config/Config.ts +++ b/src/util/config/Config.ts @@ -17,48 +17,48 @@ */ import { - ApiConfiguration, - CdnConfiguration, - DefaultsConfiguration, - EmailConfiguration, - EndpointConfiguration, - ExternalTokensConfiguration, - GeneralConfiguration, - GifConfiguration, - GuildConfiguration, - KafkaConfiguration, - LimitsConfiguration, - LoginConfiguration, - MetricsConfiguration, - PasswordResetConfiguration, - RabbitMQConfiguration, - RegionConfiguration, - RegisterConfiguration, - SecurityConfiguration, - TemplateConfiguration, - UserConfiguration, + ApiConfiguration, + CdnConfiguration, + DefaultsConfiguration, + EmailConfiguration, + EndpointConfiguration, + ExternalTokensConfiguration, + GeneralConfiguration, + GifConfiguration, + GuildConfiguration, + KafkaConfiguration, + LimitsConfiguration, + LoginConfiguration, + MetricsConfiguration, + PasswordResetConfiguration, + RabbitMQConfiguration, + RegionConfiguration, + RegisterConfiguration, + SecurityConfiguration, + TemplateConfiguration, + UserConfiguration, } from "./types"; export class ConfigValue { - admin: EndpointConfiguration = new EndpointConfiguration(); - gateway: EndpointConfiguration = new EndpointConfiguration(); - cdn: CdnConfiguration = new CdnConfiguration(); - api: ApiConfiguration = new ApiConfiguration(); - general: GeneralConfiguration = new GeneralConfiguration(); - limits: LimitsConfiguration = new LimitsConfiguration(); - security: SecurityConfiguration = new SecurityConfiguration(); - login: LoginConfiguration = new LoginConfiguration(); - register: RegisterConfiguration = new RegisterConfiguration(); - regions: RegionConfiguration = new RegionConfiguration(); - guild: GuildConfiguration = new GuildConfiguration(); - gif: GifConfiguration = new GifConfiguration(); - rabbitmq: RabbitMQConfiguration = new RabbitMQConfiguration(); - kafka: KafkaConfiguration = new KafkaConfiguration(); - templates: TemplateConfiguration = new TemplateConfiguration(); - metrics: MetricsConfiguration = new MetricsConfiguration(); - defaults: DefaultsConfiguration = new DefaultsConfiguration(); - external: ExternalTokensConfiguration = new ExternalTokensConfiguration(); - email: EmailConfiguration = new EmailConfiguration(); - passwordReset: PasswordResetConfiguration = new PasswordResetConfiguration(); - user: UserConfiguration = new UserConfiguration(); + admin: EndpointConfiguration = new EndpointConfiguration(); + gateway: EndpointConfiguration = new EndpointConfiguration(); + cdn: CdnConfiguration = new CdnConfiguration(); + api: ApiConfiguration = new ApiConfiguration(); + general: GeneralConfiguration = new GeneralConfiguration(); + limits: LimitsConfiguration = new LimitsConfiguration(); + security: SecurityConfiguration = new SecurityConfiguration(); + login: LoginConfiguration = new LoginConfiguration(); + register: RegisterConfiguration = new RegisterConfiguration(); + regions: RegionConfiguration = new RegionConfiguration(); + guild: GuildConfiguration = new GuildConfiguration(); + gif: GifConfiguration = new GifConfiguration(); + rabbitmq: RabbitMQConfiguration = new RabbitMQConfiguration(); + kafka: KafkaConfiguration = new KafkaConfiguration(); + templates: TemplateConfiguration = new TemplateConfiguration(); + metrics: MetricsConfiguration = new MetricsConfiguration(); + defaults: DefaultsConfiguration = new DefaultsConfiguration(); + external: ExternalTokensConfiguration = new ExternalTokensConfiguration(); + email: EmailConfiguration = new EmailConfiguration(); + passwordReset: PasswordResetConfiguration = new PasswordResetConfiguration(); + user: UserConfiguration = new UserConfiguration(); } diff --git a/src/util/config/types/ApiConfiguration.ts b/src/util/config/types/ApiConfiguration.ts index f865a9b..9b01199 100644 --- a/src/util/config/types/ApiConfiguration.ts +++ b/src/util/config/types/ApiConfiguration.ts @@ -19,6 +19,6 @@ import { EndpointConfiguration } from "./EndpointConfiguration"; export class ApiConfiguration extends EndpointConfiguration { - defaultVersion: string = "9"; - activeVersions: string[] = ["6", "7", "8", "9"]; + defaultVersion: string = "9"; + activeVersions: string[] = ["6", "7", "8", "9"]; } diff --git a/src/util/config/types/CdnConfiguration.ts b/src/util/config/types/CdnConfiguration.ts index e821e7a..f93307a 100644 --- a/src/util/config/types/CdnConfiguration.ts +++ b/src/util/config/types/CdnConfiguration.ts @@ -19,9 +19,9 @@ import { EndpointConfiguration } from "./EndpointConfiguration"; export class CdnConfiguration extends EndpointConfiguration { - resizeHeightMax: number = 1000; - resizeWidthMax: number = 1000; - imagorServerUrl: string | null = null; - proxyCacheHeaderSeconds: number = 60 * 60 * 24; - maxAttachmentSize: number = 25 * 1024 * 1024; // 25 MB + resizeHeightMax: number = 1000; + resizeWidthMax: number = 1000; + imagorServerUrl: string | null = null; + proxyCacheHeaderSeconds: number = 60 * 60 * 24; + maxAttachmentSize: number = 25 * 1024 * 1024; // 25 MB } diff --git a/src/util/config/types/DefaultsConfiguration.ts b/src/util/config/types/DefaultsConfiguration.ts index 67d7d62..858ffcf 100644 --- a/src/util/config/types/DefaultsConfiguration.ts +++ b/src/util/config/types/DefaultsConfiguration.ts @@ -19,6 +19,6 @@ import { GuildDefaults, UserDefaults } from "."; export class DefaultsConfiguration { - guild: GuildDefaults = new GuildDefaults(); - user: UserDefaults = new UserDefaults(); + guild: GuildDefaults = new GuildDefaults(); + user: UserDefaults = new UserDefaults(); } diff --git a/src/util/config/types/EmailConfiguration.ts b/src/util/config/types/EmailConfiguration.ts index a430cb1..acd98d1 100644 --- a/src/util/config/types/EmailConfiguration.ts +++ b/src/util/config/types/EmailConfiguration.ts @@ -20,10 +20,10 @@ import { SendGridConfiguration } from "./subconfigurations/email/SendGrid"; export class EmailConfiguration { - provider: string | null = null; - senderAddress: string | null = null; - smtp: SMTPConfiguration = new SMTPConfiguration(); - mailgun: MailGunConfiguration = new MailGunConfiguration(); - mailjet: MailJetConfiguration = new MailJetConfiguration(); - sendgrid: SendGridConfiguration = new SendGridConfiguration(); + provider: string | null = null; + senderAddress: string | null = null; + smtp: SMTPConfiguration = new SMTPConfiguration(); + mailgun: MailGunConfiguration = new MailGunConfiguration(); + mailjet: MailJetConfiguration = new MailJetConfiguration(); + sendgrid: SendGridConfiguration = new SendGridConfiguration(); } diff --git a/src/util/config/types/EndpointConfiguration.ts b/src/util/config/types/EndpointConfiguration.ts index 359d2e4..b1bccd2 100644 --- a/src/util/config/types/EndpointConfiguration.ts +++ b/src/util/config/types/EndpointConfiguration.ts @@ -17,6 +17,6 @@ */ export class EndpointConfiguration { - endpointPrivate: string | null = null; - endpointPublic: string | null = null; + endpointPrivate: string | null = null; + endpointPublic: string | null = null; } diff --git a/src/util/config/types/ExternalTokensConfiguration.ts b/src/util/config/types/ExternalTokensConfiguration.ts index 72cb0e7..7a1a40c 100644 --- a/src/util/config/types/ExternalTokensConfiguration.ts +++ b/src/util/config/types/ExternalTokensConfiguration.ts @@ -17,5 +17,5 @@ */ export class ExternalTokensConfiguration { - twitter: string | null = null; + twitter: string | null = null; } diff --git a/src/util/config/types/GeneralConfiguration.ts b/src/util/config/types/GeneralConfiguration.ts index 1548b52..beafe01 100644 --- a/src/util/config/types/GeneralConfiguration.ts +++ b/src/util/config/types/GeneralConfiguration.ts @@ -19,13 +19,13 @@ import { Snowflake } from "@spacebar/util"; export class GeneralConfiguration { - instanceName: string = "Spacebar Instance"; - instanceDescription: string | null = "This is a Spacebar instance made in the pre-release days"; - frontPage: string | null = null; - tosPage: string | null = null; - correspondenceEmail: string | null = null; - correspondenceUserID: string | null = null; - image: string | null = null; - instanceId: string = Snowflake.generate(); - autoCreateBotUsers: boolean = false; + instanceName: string = "Spacebar Instance"; + instanceDescription: string | null = "This is a Spacebar instance made in the pre-release days"; + frontPage: string | null = null; + tosPage: string | null = null; + correspondenceEmail: string | null = null; + correspondenceUserID: string | null = null; + image: string | null = null; + instanceId: string = Snowflake.generate(); + autoCreateBotUsers: boolean = false; } diff --git a/src/util/config/types/GifConfiguration.ts b/src/util/config/types/GifConfiguration.ts index 5f3c658..04afb3b 100644 --- a/src/util/config/types/GifConfiguration.ts +++ b/src/util/config/types/GifConfiguration.ts @@ -17,7 +17,7 @@ */ export class GifConfiguration { - enabled: boolean = true; - provider = "tenor" as const; // more coming soon - apiKey?: string = "LIVDSRZULELA"; + enabled: boolean = true; + provider = "tenor" as const; // more coming soon + apiKey?: string = "LIVDSRZULELA"; } diff --git a/src/util/config/types/GuildConfiguration.ts b/src/util/config/types/GuildConfiguration.ts index 221b114..b26707b 100644 --- a/src/util/config/types/GuildConfiguration.ts +++ b/src/util/config/types/GuildConfiguration.ts @@ -19,7 +19,7 @@ import { DiscoveryConfiguration, AutoJoinConfiguration } from "."; export class GuildConfiguration { - discovery: DiscoveryConfiguration = new DiscoveryConfiguration(); - autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration(); - defaultFeatures: string[] = []; + discovery: DiscoveryConfiguration = new DiscoveryConfiguration(); + autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration(); + defaultFeatures: string[] = []; } diff --git a/src/util/config/types/KafkaConfiguration.ts b/src/util/config/types/KafkaConfiguration.ts index b1e845f..b2eadd9 100644 --- a/src/util/config/types/KafkaConfiguration.ts +++ b/src/util/config/types/KafkaConfiguration.ts @@ -19,5 +19,5 @@ import { KafkaBroker } from "."; export class KafkaConfiguration { - brokers: KafkaBroker[] | null = null; + brokers: KafkaBroker[] | null = null; } diff --git a/src/util/config/types/LimitConfigurations.ts b/src/util/config/types/LimitConfigurations.ts index a9f9c29..d3cc19f 100644 --- a/src/util/config/types/LimitConfigurations.ts +++ b/src/util/config/types/LimitConfigurations.ts @@ -19,10 +19,10 @@ import { ChannelLimits, GlobalRateLimits, GuildLimits, MessageLimits, RateLimits, UserLimits } from "."; export class LimitsConfiguration { - user: UserLimits = new UserLimits(); - guild: GuildLimits = new GuildLimits(); - message: MessageLimits = new MessageLimits(); - channel: ChannelLimits = new ChannelLimits(); - rate: RateLimits = new RateLimits(); - absoluteRate: GlobalRateLimits = new GlobalRateLimits(); + user: UserLimits = new UserLimits(); + guild: GuildLimits = new GuildLimits(); + message: MessageLimits = new MessageLimits(); + channel: ChannelLimits = new ChannelLimits(); + rate: RateLimits = new RateLimits(); + absoluteRate: GlobalRateLimits = new GlobalRateLimits(); } diff --git a/src/util/config/types/LoginConfiguration.ts b/src/util/config/types/LoginConfiguration.ts index ce9c85b..07de530 100644 --- a/src/util/config/types/LoginConfiguration.ts +++ b/src/util/config/types/LoginConfiguration.ts @@ -17,6 +17,6 @@ */ export class LoginConfiguration { - requireCaptcha: boolean = false; - requireVerification: boolean = false; + requireCaptcha: boolean = false; + requireVerification: boolean = false; } diff --git a/src/util/config/types/MetricsConfiguration.ts b/src/util/config/types/MetricsConfiguration.ts index 3f3c2f4..c7cf28f 100644 --- a/src/util/config/types/MetricsConfiguration.ts +++ b/src/util/config/types/MetricsConfiguration.ts @@ -17,5 +17,5 @@ */ export class MetricsConfiguration { - timeout: number = 30000; + timeout: number = 30000; } diff --git a/src/util/config/types/PasswordResetConfiguration.ts b/src/util/config/types/PasswordResetConfiguration.ts index 58db7a6..3c4786f 100644 --- a/src/util/config/types/PasswordResetConfiguration.ts +++ b/src/util/config/types/PasswordResetConfiguration.ts @@ -17,5 +17,5 @@ */ export class PasswordResetConfiguration { - requireCaptcha: boolean = false; + requireCaptcha: boolean = false; } diff --git a/src/util/config/types/RabbitMQConfiguration.ts b/src/util/config/types/RabbitMQConfiguration.ts index d1db6fb..468ffcd 100644 --- a/src/util/config/types/RabbitMQConfiguration.ts +++ b/src/util/config/types/RabbitMQConfiguration.ts @@ -17,5 +17,5 @@ */ export class RabbitMQConfiguration { - host: string | null = null; + host: string | null = null; } diff --git a/src/util/config/types/RegionConfiguration.ts b/src/util/config/types/RegionConfiguration.ts index b6c11b0..bf95368 100644 --- a/src/util/config/types/RegionConfiguration.ts +++ b/src/util/config/types/RegionConfiguration.ts @@ -19,16 +19,16 @@ import { Region } from "@spacebar/schemas"; export class RegionConfiguration { - default: string = "spacebar"; - useDefaultAsOptimal: boolean = true; - available: Region[] = [ - { - id: "spacebar", - name: "spacebar", - endpoint: "127.0.0.1:3004", - vip: false, - custom: false, - deprecated: false, - }, - ]; + default: string = "spacebar"; + useDefaultAsOptimal: boolean = true; + available: Region[] = [ + { + id: "spacebar", + name: "spacebar", + endpoint: "127.0.0.1:3004", + vip: false, + custom: false, + deprecated: false, + }, + ]; } diff --git a/src/util/config/types/RegisterConfiguration.ts b/src/util/config/types/RegisterConfiguration.ts index 9d76434..31d89db 100644 --- a/src/util/config/types/RegisterConfiguration.ts +++ b/src/util/config/types/RegisterConfiguration.ts @@ -19,21 +19,21 @@ import { DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration } from "."; export class RegisterConfiguration { - email: RegistrationEmailConfiguration = new RegistrationEmailConfiguration(); - dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration(); - password: PasswordConfiguration = new PasswordConfiguration(); - disabled: boolean = false; - requireCaptcha: boolean = true; - requireInvite: boolean = false; - guestsRequireInvite: boolean = true; - allowNewRegistration: boolean = true; - allowMultipleAccounts: boolean = true; - blockProxies: boolean = true; - blockIpDataCoThreatTypes: string[] = ["tor", "icloud_relay", "proxy", "datacenter", "anonymous", "known_attacker", "known_abuser", "threat"]; // matching ipdata's threat.is_* fields as of 2025/11/30, minus bogon - blockAsnTypes: string[] = [""]; - blockAsns: string[] = [""]; - blockAbuseIpDbAboveScore: number = 75; // 0 to disable - incrementingDiscriminators: boolean = false; // random otherwise - defaultRights: string = "875069521787904"; // See `npm run generate:rights` - checkIp: boolean = true; + email: RegistrationEmailConfiguration = new RegistrationEmailConfiguration(); + dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration(); + password: PasswordConfiguration = new PasswordConfiguration(); + disabled: boolean = false; + requireCaptcha: boolean = true; + requireInvite: boolean = false; + guestsRequireInvite: boolean = true; + allowNewRegistration: boolean = true; + allowMultipleAccounts: boolean = true; + blockProxies: boolean = true; + blockIpDataCoThreatTypes: string[] = ["tor", "icloud_relay", "proxy", "datacenter", "anonymous", "known_attacker", "known_abuser", "threat"]; // matching ipdata's threat.is_* fields as of 2025/11/30, minus bogon + blockAsnTypes: string[] = [""]; + blockAsns: string[] = [""]; + blockAbuseIpDbAboveScore: number = 75; // 0 to disable + incrementingDiscriminators: boolean = false; // random otherwise + defaultRights: string = "875069521787904"; // See `npm run generate:rights` + checkIp: boolean = true; } diff --git a/src/util/config/types/SecurityConfiguration.ts b/src/util/config/types/SecurityConfiguration.ts index 1f3a191..7a15f89 100644 --- a/src/util/config/types/SecurityConfiguration.ts +++ b/src/util/config/types/SecurityConfiguration.ts @@ -20,30 +20,30 @@ import { CaptchaConfiguration, TwoFactorConfiguration } from "."; export class SecurityConfiguration { - captcha: CaptchaConfiguration = new CaptchaConfiguration(); - twoFactor: TwoFactorConfiguration = new TwoFactorConfiguration(); - autoUpdate: boolean | number = true; - requestSignature: string = crypto.randomBytes(32).toString("base64"); - jwtSecret: string | null = null; - // header to get the real user ip address - // X-Forwarded-For for nginx/reverse proxies - // CF-Connecting-IP for cloudflare - forwardedFor: string | null = null; - // trusted proxies to get the real user ip address - // requires a reverse proxy to overwrite X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto - trustedProxies: string | boolean | null = null; - abuseIpDbApiKey: string | null = null; - // https://docs.abuseipdb.com/#api-daily-rate-limits - abuseipdbBlacklistRatelimit: number = 5; - abuseipdbConfidenceScoreTreshold: number = 50; - ipdataApiKey: string | null = null; - mfaBackupCodeCount: number = 10; - statsWorldReadable: boolean = true; - defaultRegistrationTokenExpiration: number = 1000 * 60 * 60 * 24 * 7; //1 week - // cdn signed urls - cdnSignUrls: boolean = false; - cdnSignatureKey: string = crypto.randomBytes(32).toString("base64"); - cdnSignatureDuration: string = "24h"; - cdnSignatureIncludeIp: boolean = true; - cdnSignatureIncludeUserAgent: boolean = true; + captcha: CaptchaConfiguration = new CaptchaConfiguration(); + twoFactor: TwoFactorConfiguration = new TwoFactorConfiguration(); + autoUpdate: boolean | number = true; + requestSignature: string = crypto.randomBytes(32).toString("base64"); + jwtSecret: string | null = null; + // header to get the real user ip address + // X-Forwarded-For for nginx/reverse proxies + // CF-Connecting-IP for cloudflare + forwardedFor: string | null = null; + // trusted proxies to get the real user ip address + // requires a reverse proxy to overwrite X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto + trustedProxies: string | boolean | null = null; + abuseIpDbApiKey: string | null = null; + // https://docs.abuseipdb.com/#api-daily-rate-limits + abuseipdbBlacklistRatelimit: number = 5; + abuseipdbConfidenceScoreTreshold: number = 50; + ipdataApiKey: string | null = null; + mfaBackupCodeCount: number = 10; + statsWorldReadable: boolean = true; + defaultRegistrationTokenExpiration: number = 1000 * 60 * 60 * 24 * 7; //1 week + // cdn signed urls + cdnSignUrls: boolean = false; + cdnSignatureKey: string = crypto.randomBytes(32).toString("base64"); + cdnSignatureDuration: string = "24h"; + cdnSignatureIncludeIp: boolean = true; + cdnSignatureIncludeUserAgent: boolean = true; } diff --git a/src/util/config/types/TemplateConfiguration.ts b/src/util/config/types/TemplateConfiguration.ts index dd1c3cb..d052765 100644 --- a/src/util/config/types/TemplateConfiguration.ts +++ b/src/util/config/types/TemplateConfiguration.ts @@ -17,8 +17,8 @@ */ export class TemplateConfiguration { - enabled: boolean = true; - allowTemplateCreation: boolean = true; - allowDiscordTemplates: boolean = true; - allowRaws: boolean = true; + enabled: boolean = true; + allowTemplateCreation: boolean = true; + allowDiscordTemplates: boolean = true; + allowRaws: boolean = true; } diff --git a/src/util/config/types/UsersConfiguration.ts b/src/util/config/types/UsersConfiguration.ts index 03e866d..10bb6f4 100755 --- a/src/util/config/types/UsersConfiguration.ts +++ b/src/util/config/types/UsersConfiguration.ts @@ -17,6 +17,6 @@ */ export class UserConfiguration { - blockedContains: string[] = ["discord", "clyde", "spacebar"]; - blockedEquals: string[] = ["everyone", "here"]; + blockedContains: string[] = ["discord", "clyde", "spacebar"]; + blockedEquals: string[] = ["everyone", "here"]; } diff --git a/src/util/config/types/subconfigurations/client/ClientReleaseConfiguration.ts b/src/util/config/types/subconfigurations/client/ClientReleaseConfiguration.ts index 3a0c696..6f277e3 100644 --- a/src/util/config/types/subconfigurations/client/ClientReleaseConfiguration.ts +++ b/src/util/config/types/subconfigurations/client/ClientReleaseConfiguration.ts @@ -17,6 +17,6 @@ */ export class ClientReleaseConfiguration { - useLocalRelease: boolean = true; //TODO - upstreamVersion: string = "0.0.264"; + useLocalRelease: boolean = true; //TODO + upstreamVersion: string = "0.0.264"; } diff --git a/src/util/config/types/subconfigurations/defaults/GuildDefaults.ts b/src/util/config/types/subconfigurations/defaults/GuildDefaults.ts index 2ee67ab..56bde41 100644 --- a/src/util/config/types/subconfigurations/defaults/GuildDefaults.ts +++ b/src/util/config/types/subconfigurations/defaults/GuildDefaults.ts @@ -17,9 +17,9 @@ */ export class GuildDefaults { - maxPresences: number = 250000; - maxVideoChannelUsers: number = 200; - afkTimeout: number = 300; - defaultMessageNotifications: number = 1; - explicitContentFilter: number = 0; + maxPresences: number = 250000; + maxVideoChannelUsers: number = 200; + afkTimeout: number = 300; + defaultMessageNotifications: number = 1; + explicitContentFilter: number = 0; } diff --git a/src/util/config/types/subconfigurations/defaults/UserDefaults.ts b/src/util/config/types/subconfigurations/defaults/UserDefaults.ts index 86f2271..adf6764 100644 --- a/src/util/config/types/subconfigurations/defaults/UserDefaults.ts +++ b/src/util/config/types/subconfigurations/defaults/UserDefaults.ts @@ -17,7 +17,7 @@ */ export class UserDefaults { - premium: boolean = true; - premiumType: number = 2; - verified: boolean = true; + premium: boolean = true; + premiumType: number = 2; + verified: boolean = true; } diff --git a/src/util/config/types/subconfigurations/email/MailGun.ts b/src/util/config/types/subconfigurations/email/MailGun.ts index a559be2..b66ed74 100644 --- a/src/util/config/types/subconfigurations/email/MailGun.ts +++ b/src/util/config/types/subconfigurations/email/MailGun.ts @@ -17,8 +17,8 @@ */ export class MailGunConfiguration { - username: string | null = null; - apiKey: string | null = null; - domain: string | null = null; - isEuropean: boolean = true; + username: string | null = null; + apiKey: string | null = null; + domain: string | null = null; + isEuropean: boolean = true; } diff --git a/src/util/config/types/subconfigurations/email/MailJet.ts b/src/util/config/types/subconfigurations/email/MailJet.ts index 36b3a56..32dfb40 100644 --- a/src/util/config/types/subconfigurations/email/MailJet.ts +++ b/src/util/config/types/subconfigurations/email/MailJet.ts @@ -17,6 +17,6 @@ */ export class MailJetConfiguration { - apiKey: string | null = null; - apiSecret: string | null = null; + apiKey: string | null = null; + apiSecret: string | null = null; } diff --git a/src/util/config/types/subconfigurations/email/SMTP.ts b/src/util/config/types/subconfigurations/email/SMTP.ts index 8bd57d2..85c286a 100644 --- a/src/util/config/types/subconfigurations/email/SMTP.ts +++ b/src/util/config/types/subconfigurations/email/SMTP.ts @@ -17,9 +17,9 @@ */ export class SMTPConfiguration { - host: string | null = null; - port: number | null = null; - secure: boolean | null = null; - username: string | null = null; - password: string | null = null; + host: string | null = null; + port: number | null = null; + secure: boolean | null = null; + username: string | null = null; + password: string | null = null; } diff --git a/src/util/config/types/subconfigurations/email/SendGrid.ts b/src/util/config/types/subconfigurations/email/SendGrid.ts index a3a26c7..18115e3 100644 --- a/src/util/config/types/subconfigurations/email/SendGrid.ts +++ b/src/util/config/types/subconfigurations/email/SendGrid.ts @@ -17,5 +17,5 @@ */ export class SendGridConfiguration { - apiKey: string | null = null; + apiKey: string | null = null; } diff --git a/src/util/config/types/subconfigurations/guild/AutoJoin.ts b/src/util/config/types/subconfigurations/guild/AutoJoin.ts index 5acdd74..2843660 100644 --- a/src/util/config/types/subconfigurations/guild/AutoJoin.ts +++ b/src/util/config/types/subconfigurations/guild/AutoJoin.ts @@ -17,8 +17,8 @@ */ export class AutoJoinConfiguration { - enabled: boolean = true; - guilds: string[] = []; - canLeave: boolean = true; - bots: boolean = false; + enabled: boolean = true; + guilds: string[] = []; + canLeave: boolean = true; + bots: boolean = false; } diff --git a/src/util/config/types/subconfigurations/guild/Discovery.ts b/src/util/config/types/subconfigurations/guild/Discovery.ts index d18fc6b..30724e3 100644 --- a/src/util/config/types/subconfigurations/guild/Discovery.ts +++ b/src/util/config/types/subconfigurations/guild/Discovery.ts @@ -17,8 +17,8 @@ */ export class DiscoveryConfiguration { - showAllGuilds: boolean = false; - useRecommendation: boolean = false; // TODO: Recommendation, privacy concern? - offset: number = 0; - limit: number = 24; + showAllGuilds: boolean = false; + useRecommendation: boolean = false; // TODO: Recommendation, privacy concern? + offset: number = 0; + limit: number = 24; } diff --git a/src/util/config/types/subconfigurations/kafka/KafkaBroker.ts b/src/util/config/types/subconfigurations/kafka/KafkaBroker.ts index 4736033..15548f6 100644 --- a/src/util/config/types/subconfigurations/kafka/KafkaBroker.ts +++ b/src/util/config/types/subconfigurations/kafka/KafkaBroker.ts @@ -17,6 +17,6 @@ */ export interface KafkaBroker { - ip: string; - port: number; + ip: string; + port: number; } diff --git a/src/util/config/types/subconfigurations/limits/ChannelLimits.ts b/src/util/config/types/subconfigurations/limits/ChannelLimits.ts index 0c4af04..266e6a3 100644 --- a/src/util/config/types/subconfigurations/limits/ChannelLimits.ts +++ b/src/util/config/types/subconfigurations/limits/ChannelLimits.ts @@ -17,7 +17,7 @@ */ export class ChannelLimits { - maxPins: number = 500; - maxTopic: number = 1024; - maxWebhooks: number = 100; + maxPins: number = 500; + maxTopic: number = 1024; + maxWebhooks: number = 100; } diff --git a/src/util/config/types/subconfigurations/limits/GlobalRateLimits.ts b/src/util/config/types/subconfigurations/limits/GlobalRateLimits.ts index 835d719..1af3ee5 100644 --- a/src/util/config/types/subconfigurations/limits/GlobalRateLimits.ts +++ b/src/util/config/types/subconfigurations/limits/GlobalRateLimits.ts @@ -17,20 +17,20 @@ */ export class GlobalRateLimits { - register: GlobalRateLimit = { - limit: 25, - window: 60 * 60 * 1000, - enabled: true, - }; - sendMessage: GlobalRateLimit = { - limit: 200, - window: 60 * 1000, - enabled: true, - }; + register: GlobalRateLimit = { + limit: 25, + window: 60 * 60 * 1000, + enabled: true, + }; + sendMessage: GlobalRateLimit = { + limit: 200, + window: 60 * 1000, + enabled: true, + }; } export class GlobalRateLimit { - limit: number = 100; - window: number = 60 * 60 * 1000; - enabled: boolean = true; + limit: number = 100; + window: number = 60 * 60 * 1000; + enabled: boolean = true; } diff --git a/src/util/config/types/subconfigurations/limits/GuildLimits.ts b/src/util/config/types/subconfigurations/limits/GuildLimits.ts index 48c575b..fb9f5ad 100644 --- a/src/util/config/types/subconfigurations/limits/GuildLimits.ts +++ b/src/util/config/types/subconfigurations/limits/GuildLimits.ts @@ -17,11 +17,11 @@ */ export class GuildLimits { - maxRoles: number = 1000; - maxEmojis: number = 2000; - maxStickers: number = 500; - maxMembers: number = 25000000; - maxChannels: number = 65535; - maxBulkBanUsers: number = 200; - maxChannelsInCategory: number = 65535; + maxRoles: number = 1000; + maxEmojis: number = 2000; + maxStickers: number = 500; + maxMembers: number = 25000000; + maxChannels: number = 65535; + maxBulkBanUsers: number = 200; + maxChannelsInCategory: number = 65535; } diff --git a/src/util/config/types/subconfigurations/limits/MessageLimits.ts b/src/util/config/types/subconfigurations/limits/MessageLimits.ts index f98e14b..f61c0b6 100644 --- a/src/util/config/types/subconfigurations/limits/MessageLimits.ts +++ b/src/util/config/types/subconfigurations/limits/MessageLimits.ts @@ -17,11 +17,11 @@ */ export class MessageLimits { - maxCharacters: number = 1048576; - maxTTSCharacters: number = 160; - maxReactions: number = 2048; - maxAttachmentSize: number = 1024 * 1024 * 1024; - maxBulkDelete: number = 1000; - maxEmbedDownloadSize: number = 1024 * 1024 * 5; - maxPreloadCount: number = 100; + maxCharacters: number = 1048576; + maxTTSCharacters: number = 160; + maxReactions: number = 2048; + maxAttachmentSize: number = 1024 * 1024 * 1024; + maxBulkDelete: number = 1000; + maxEmbedDownloadSize: number = 1024 * 1024 * 5; + maxPreloadCount: number = 100; } diff --git a/src/util/config/types/subconfigurations/limits/RateLimits.ts b/src/util/config/types/subconfigurations/limits/RateLimits.ts index 0ce0827..6804b69 100644 --- a/src/util/config/types/subconfigurations/limits/RateLimits.ts +++ b/src/util/config/types/subconfigurations/limits/RateLimits.ts @@ -19,18 +19,18 @@ import { RateLimitOptions, RouteRateLimit } from "."; export class RateLimits { - enabled: boolean = false; - ip: RateLimitOptions = { - count: 500, - window: 5, - }; - global: RateLimitOptions = { - count: 250, - window: 5, - }; - error: RateLimitOptions = { - count: 10, - window: 5, - }; - routes: RouteRateLimit = new RouteRateLimit(); + enabled: boolean = false; + ip: RateLimitOptions = { + count: 500, + window: 5, + }; + global: RateLimitOptions = { + count: 250, + window: 5, + }; + error: RateLimitOptions = { + count: 10, + window: 5, + }; + routes: RouteRateLimit = new RouteRateLimit(); } diff --git a/src/util/config/types/subconfigurations/limits/UserLimits.ts b/src/util/config/types/subconfigurations/limits/UserLimits.ts index afe9afb..cbe686d 100644 --- a/src/util/config/types/subconfigurations/limits/UserLimits.ts +++ b/src/util/config/types/subconfigurations/limits/UserLimits.ts @@ -17,8 +17,8 @@ */ export class UserLimits { - maxGuilds: number = 1048576; - maxUsername: number = 32; - maxFriends: number = 5000; - maxBio: number = 190; + maxGuilds: number = 1048576; + maxUsername: number = 32; + maxFriends: number = 5000; + maxBio: number = 190; } diff --git a/src/util/config/types/subconfigurations/limits/ratelimits/Auth.ts b/src/util/config/types/subconfigurations/limits/ratelimits/Auth.ts index ce83828..ffe0d55 100644 --- a/src/util/config/types/subconfigurations/limits/ratelimits/Auth.ts +++ b/src/util/config/types/subconfigurations/limits/ratelimits/Auth.ts @@ -19,12 +19,12 @@ import { RateLimitOptions } from "./RateLimitOptions"; export class AuthRateLimit { - login: RateLimitOptions = { - count: 5, - window: 60, - }; - register: RateLimitOptions = { - count: 2, - window: 60 * 60 * 12, - }; + login: RateLimitOptions = { + count: 5, + window: 60, + }; + register: RateLimitOptions = { + count: 2, + window: 60 * 60 * 12, + }; } diff --git a/src/util/config/types/subconfigurations/limits/ratelimits/RateLimitOptions.ts b/src/util/config/types/subconfigurations/limits/ratelimits/RateLimitOptions.ts index cdf8fac..87b74f3 100644 --- a/src/util/config/types/subconfigurations/limits/ratelimits/RateLimitOptions.ts +++ b/src/util/config/types/subconfigurations/limits/ratelimits/RateLimitOptions.ts @@ -17,8 +17,8 @@ */ export interface RateLimitOptions { - bot?: number; - count: number; - window: number; - onyIp?: boolean; + bot?: number; + count: number; + window: number; + onyIp?: boolean; } diff --git a/src/util/config/types/subconfigurations/limits/ratelimits/Route.ts b/src/util/config/types/subconfigurations/limits/ratelimits/Route.ts index dd0757c..e9f2c5c 100644 --- a/src/util/config/types/subconfigurations/limits/ratelimits/Route.ts +++ b/src/util/config/types/subconfigurations/limits/ratelimits/Route.ts @@ -20,18 +20,18 @@ import { RateLimitOptions } from "./RateLimitOptions"; export class RouteRateLimit { - guild: RateLimitOptions = { - count: 5, - window: 5, - }; - webhook: RateLimitOptions = { - count: 10, - window: 5, - }; - channel: RateLimitOptions = { - count: 10, - window: 5, - }; - auth: AuthRateLimit = new AuthRateLimit(); - // TODO: rate limit configuration for all routes + guild: RateLimitOptions = { + count: 5, + window: 5, + }; + webhook: RateLimitOptions = { + count: 10, + window: 5, + }; + channel: RateLimitOptions = { + count: 10, + window: 5, + }; + auth: AuthRateLimit = new AuthRateLimit(); + // TODO: rate limit configuration for all routes } diff --git a/src/util/config/types/subconfigurations/register/DateOfBirth.ts b/src/util/config/types/subconfigurations/register/DateOfBirth.ts index 4516670..b563b54 100644 --- a/src/util/config/types/subconfigurations/register/DateOfBirth.ts +++ b/src/util/config/types/subconfigurations/register/DateOfBirth.ts @@ -17,6 +17,6 @@ */ export class DateOfBirthConfiguration { - required: boolean = true; - minimum: number = 13; // in years + required: boolean = true; + minimum: number = 13; // in years } diff --git a/src/util/config/types/subconfigurations/register/Email.ts b/src/util/config/types/subconfigurations/register/Email.ts index 8d3e97c..754ac08 100644 --- a/src/util/config/types/subconfigurations/register/Email.ts +++ b/src/util/config/types/subconfigurations/register/Email.ts @@ -17,9 +17,9 @@ */ export class RegistrationEmailConfiguration { - required: boolean = false; - allowlist: boolean = false; - blocklist: boolean = true; - domains: string[] = []; // TODO: efficiently save domain blocklist in database - // domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"), + required: boolean = false; + allowlist: boolean = false; + blocklist: boolean = true; + domains: string[] = []; // TODO: efficiently save domain blocklist in database + // domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"), } diff --git a/src/util/config/types/subconfigurations/register/Password.ts b/src/util/config/types/subconfigurations/register/Password.ts index 36c3e3d..c79a4b2 100644 --- a/src/util/config/types/subconfigurations/register/Password.ts +++ b/src/util/config/types/subconfigurations/register/Password.ts @@ -17,9 +17,9 @@ */ export class PasswordConfiguration { - required: boolean = false; - minLength: number = 8; - minNumbers: number = 2; - minUpperCase: number = 2; - minSymbols: number = 0; + required: boolean = false; + minLength: number = 8; + minNumbers: number = 2; + minUpperCase: number = 2; + minSymbols: number = 0; } diff --git a/src/util/config/types/subconfigurations/security/Captcha.ts b/src/util/config/types/subconfigurations/security/Captcha.ts index 8c7c106..4cd3944 100644 --- a/src/util/config/types/subconfigurations/security/Captcha.ts +++ b/src/util/config/types/subconfigurations/security/Captcha.ts @@ -17,8 +17,8 @@ */ export class CaptchaConfiguration { - enabled: boolean = false; - service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom - sitekey: string | null = null; - secret: string | null = null; + enabled: boolean = false; + service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom + sitekey: string | null = null; + secret: string | null = null; } diff --git a/src/util/config/types/subconfigurations/security/TwoFactor.ts b/src/util/config/types/subconfigurations/security/TwoFactor.ts index 7575712..c517fa1 100644 --- a/src/util/config/types/subconfigurations/security/TwoFactor.ts +++ b/src/util/config/types/subconfigurations/security/TwoFactor.ts @@ -17,5 +17,5 @@ */ export class TwoFactorConfiguration { - generateBackupCodes: boolean = true; + generateBackupCodes: boolean = true; } diff --git a/src/util/connections/Connection.ts b/src/util/connections/Connection.ts index 7eb8bdd..afb33ac 100644 --- a/src/util/connections/Connection.ts +++ b/src/util/connections/Connection.ts @@ -25,88 +25,88 @@ * A connection that can be used to connect to an external service. */ export abstract class Connection { - id: string; - settings: { enabled: boolean }; - states: Map = new Map(); + id: string; + settings: { enabled: boolean }; + states: Map = new Map(); - abstract init(): void; + abstract init(): void; - /** - * Generates an authorization url for the connection. - * @param args - */ - abstract getAuthorizationUrl(userId: string): string; + /** + * Generates an authorization url for the connection. + * @param args + */ + abstract getAuthorizationUrl(userId: string): string; - /** - * Returns the redirect_uri for a connection type - * @returns redirect_uri for this connection - */ - getRedirectUri() { - return `${Config.get().general.frontPage}/connections/${this.id}/callback`; - } + /** + * Returns the redirect_uri for a connection type + * @returns redirect_uri for this connection + */ + getRedirectUri() { + return `${Config.get().general.frontPage}/connections/${this.id}/callback`; + } - /** - * Processes the callback - * @param args Callback arguments - */ - abstract handleCallback(params: ConnectionCallbackSchema): Promise; + /** + * Processes the callback + * @param args Callback arguments + */ + abstract handleCallback(params: ConnectionCallbackSchema): Promise; - /** - * Gets a user id from state - * @param state the state to get the user id from - * @returns the user id associated with the state - */ - getUserId(state: string): string { - if (!this.states.has(state)) throw DiscordApiErrors.INVALID_OAUTH_STATE; - return this.states.get(state) as string; - } + /** + * Gets a user id from state + * @param state the state to get the user id from + * @returns the user id associated with the state + */ + getUserId(state: string): string { + if (!this.states.has(state)) throw DiscordApiErrors.INVALID_OAUTH_STATE; + return this.states.get(state) as string; + } - /** - * Generates a state - * @param user_id The user id to generate a state for. - * @returns a new state - */ - createState(userId: string): string { - const state = crypto.randomBytes(16).toString("hex"); - this.states.set(state, userId); + /** + * Generates a state + * @param user_id The user id to generate a state for. + * @returns a new state + */ + createState(userId: string): string { + const state = crypto.randomBytes(16).toString("hex"); + this.states.set(state, userId); - return state; - } + return state; + } - /** - * Takes a state and checks if it is valid, and deletes it. - * @param state The state to check. - */ - validateState(state: string): void { - if (!this.states.has(state)) throw DiscordApiErrors.INVALID_OAUTH_STATE; - this.states.delete(state); - } + /** + * Takes a state and checks if it is valid, and deletes it. + * @param state The state to check. + */ + validateState(state: string): void { + if (!this.states.has(state)) throw DiscordApiErrors.INVALID_OAUTH_STATE; + this.states.delete(state); + } - /** - * Creates a Connected Account in the database. - * @param data connected account data - * @returns the new connected account - */ - async createConnection(data: ConnectedAccountSchema): Promise { - const ca = ConnectedAccount.create({ ...data }); - await ca.save(); - return ca; - } + /** + * Creates a Connected Account in the database. + * @param data connected account data + * @returns the new connected account + */ + async createConnection(data: ConnectedAccountSchema): Promise { + const ca = ConnectedAccount.create({ ...data }); + await ca.save(); + return ca; + } - /** - * Checks if a user has an exist connected account for the given extenal id. - * @param userId the user id - * @param externalId the connection id to find - * @returns - */ - async hasConnection(userId: string, externalId: string): Promise { - const existing = await ConnectedAccount.findOne({ - where: { - user_id: userId, - external_id: externalId, - }, - }); + /** + * Checks if a user has an exist connected account for the given extenal id. + * @param userId the user id + * @param externalId the connection id to find + * @returns + */ + async hasConnection(userId: string, externalId: string): Promise { + const existing = await ConnectedAccount.findOne({ + where: { + user_id: userId, + external_id: externalId, + }, + }); - return !!existing; - } + return !!existing; + } } diff --git a/src/util/connections/ConnectionConfig.ts b/src/util/connections/ConnectionConfig.ts index 1f9f874..a5e0095 100644 --- a/src/util/connections/ConnectionConfig.ts +++ b/src/util/connections/ConnectionConfig.ts @@ -24,70 +24,70 @@ let pairs: ConnectionConfigEntity[]; export const ConnectionConfig = { - init: async function init() { - if (config) return config; - console.log("[Connections] Loading configuration..."); - pairs = await ConnectionConfigEntity.find(); - config = pairsToConfig(pairs); + init: async function init() { + if (config) return config; + console.log("[Connections] Loading configuration..."); + pairs = await ConnectionConfigEntity.find(); + config = pairsToConfig(pairs); - return this.set(config); - }, - get: function get() { - if (!config) { - return {}; - } - return config; - }, - set: function set(val: Partial) { - if (!config || !val) return; - config = OrmUtils.mergeDeep(config, val); + return this.set(config); + }, + get: function get() { + if (!config) { + return {}; + } + return config; + }, + set: function set(val: Partial) { + if (!config || !val) return; + config = OrmUtils.mergeDeep(config, val); - // return applyConfig(config); - return applyConfig(val); - }, + // return applyConfig(config); + return applyConfig(val); + }, }; function applyConfig(val: any) { - async function apply(obj: any, key = ""): Promise { - if (typeof obj === "object" && obj !== null && !(obj instanceof Date)) return Promise.all(Object.keys(obj).map((k) => apply(obj[k], key ? `${key}_${k}` : k))); + async function apply(obj: any, key = ""): Promise { + if (typeof obj === "object" && obj !== null && !(obj instanceof Date)) return Promise.all(Object.keys(obj).map((k) => apply(obj[k], key ? `${key}_${k}` : k))); - let pair = pairs.find((x) => x.key === key); - if (!pair) pair = new ConnectionConfigEntity(); + let pair = pairs.find((x) => x.key === key); + if (!pair) pair = new ConnectionConfigEntity(); - pair.key = key; + pair.key = key; - if (pair.value !== obj) { - pair.value = obj; - if (!pair.key || pair.key == null) { - console.log(`[Connections] WARN: Empty config key`); - console.log(pair); - } else return pair.save(); - } - } + if (pair.value !== obj) { + pair.value = obj; + if (!pair.key || pair.key == null) { + console.log(`[Connections] WARN: Empty config key`); + console.log(pair); + } else return pair.save(); + } + } - return apply(val); + return apply(val); } function pairsToConfig(pairs: ConnectionConfigEntity[]) { - const value: any = {}; + const value: any = {}; - pairs.forEach((p) => { - const keys = p.key.split("_"); - let obj = value; - let prev = ""; - let prevObj = obj; - let i = 0; + pairs.forEach((p) => { + const keys = p.key.split("_"); + let obj = value; + let prev = ""; + let prevObj = obj; + let i = 0; - for (const key of keys) { - if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = []; - if (i++ === keys.length - 1) obj[key] = p.value; - else if (!obj[key]) obj[key] = {}; + for (const key of keys) { + if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = []; + if (i++ === keys.length - 1) obj[key] = p.value; + else if (!obj[key]) obj[key] = {}; - prev = key; - prevObj = obj; - obj = obj[key]; - } - }); + prev = key; + prevObj = obj; + obj = obj[key]; + } + }); - return value; + return value; } diff --git a/src/util/connections/ConnectionLoader.ts b/src/util/connections/ConnectionLoader.ts index 6a7a5d1..609c53b 100644 --- a/src/util/connections/ConnectionLoader.ts +++ b/src/util/connections/ConnectionLoader.ts @@ -26,52 +26,52 @@ const connectionsLoaded = false; export class ConnectionLoader { - public static async loadConnections() { - if (connectionsLoaded) return; - ConnectionConfig.init(); - const dirs = fs.readdirSync(root).filter((x) => { - try { - fs.readdirSync(path.join(root, x)); - return true; - } catch (e) { - return false; - } - }); + public static async loadConnections() { + if (connectionsLoaded) return; + ConnectionConfig.init(); + const dirs = fs.readdirSync(root).filter((x) => { + try { + fs.readdirSync(path.join(root, x)); + return true; + } catch (e) { + return false; + } + }); - dirs.forEach(async (x) => { - const modPath = path.resolve(path.join(root, x)); - const mod = new (require(modPath).default)() as Connection; - ConnectionStore.connections.set(mod.id, mod); + dirs.forEach(async (x) => { + const modPath = path.resolve(path.join(root, x)); + const mod = new (require(modPath).default)() as Connection; + ConnectionStore.connections.set(mod.id, mod); - mod.init(); - // console.log(`[Connections] Loaded connection '${mod.id}'`); - }); - } + mod.init(); + // console.log(`[Connections] Loaded connection '${mod.id}'`); + }); + } - public static getConnectionConfig(id: string, defaults?: unknown): T { - let cfg = ConnectionConfig.get()[id]; - if (defaults) { - if (cfg) cfg = Object.assign({}, defaults, cfg); - else { - cfg = defaults; - this.setConnectionConfig(id, cfg); - } - } + public static getConnectionConfig(id: string, defaults?: unknown): T { + let cfg = ConnectionConfig.get()[id]; + if (defaults) { + if (cfg) cfg = Object.assign({}, defaults, cfg); + else { + cfg = defaults; + this.setConnectionConfig(id, cfg); + } + } - if (cfg?.enabled) console.log(`[Connections] ${id} enabled`); + if (cfg?.enabled) console.log(`[Connections] ${id} enabled`); - // if (!cfg) - // console.log( - // `[ConnectionConfig/WARN] Getting connection settings for '${id}' returned null! (Did you forget to add settings?)`, - // ); - return cfg; - } + // if (!cfg) + // console.log( + // `[ConnectionConfig/WARN] Getting connection settings for '${id}' returned null! (Did you forget to add settings?)`, + // ); + return cfg; + } - public static async setConnectionConfig(id: string, config: Partial): Promise { - if (!config) console.warn(`[Connections/WARN] ${id} tried to set config=null!`); + public static async setConnectionConfig(id: string, config: Partial): Promise { + if (!config) console.warn(`[Connections/WARN] ${id} tried to set config=null!`); - await ConnectionConfig.set({ - [id]: Object.assign(config, ConnectionLoader.getConnectionConfig(id) || {}), - }); - } + await ConnectionConfig.set({ + [id]: Object.assign(config, ConnectionLoader.getConnectionConfig(id) || {}), + }); + } } diff --git a/src/util/connections/ConnectionStore.ts b/src/util/connections/ConnectionStore.ts index 0cc4dc3..7649610 100644 --- a/src/util/connections/ConnectionStore.ts +++ b/src/util/connections/ConnectionStore.ts @@ -20,5 +20,5 @@ import { RefreshableConnection } from "./RefreshableConnection"; export class ConnectionStore { - public static connections: Map = new Map(); + public static connections: Map = new Map(); } diff --git a/src/util/connections/RefreshableConnection.ts b/src/util/connections/RefreshableConnection.ts index 08b01c6..32cf36a 100644 --- a/src/util/connections/RefreshableConnection.ts +++ b/src/util/connections/RefreshableConnection.ts @@ -24,22 +24,22 @@ * A connection that can refresh its token. */ export abstract class RefreshableConnection extends Connection { - refreshEnabled = true; + refreshEnabled = true; - /** - * Refreshes the token for a connected account. - * @param connectedAccount The connected account to refresh - */ - abstract refreshToken(connectedAccount: ConnectedAccount): Promise; + /** + * Refreshes the token for a connected account. + * @param connectedAccount The connected account to refresh + */ + abstract refreshToken(connectedAccount: ConnectedAccount): Promise; - /** - * Refreshes the token for a connected account and saves it to the database. - * @param connectedAccount The connected account to refresh - */ - async refresh(connectedAccount: ConnectedAccount): Promise { - const tokenData = await this.refreshToken(connectedAccount); - connectedAccount.token_data = { ...tokenData, fetched_at: Date.now() }; - await connectedAccount.save(); - return tokenData; - } + /** + * Refreshes the token for a connected account and saves it to the database. + * @param connectedAccount The connected account to refresh + */ + async refresh(connectedAccount: ConnectedAccount): Promise { + const tokenData = await this.refreshToken(connectedAccount); + connectedAccount.token_data = { ...tokenData, fetched_at: Date.now() }; + await connectedAccount.save(); + return tokenData; + } } diff --git a/src/util/dtos/ConnectedAccountDTO.ts b/src/util/dtos/ConnectedAccountDTO.ts index 4c43b9d..a137d73 100644 --- a/src/util/dtos/ConnectedAccountDTO.ts +++ b/src/util/dtos/ConnectedAccountDTO.ts @@ -19,35 +19,35 @@ import { ConnectedAccount } from "../entities"; export class ConnectedAccountDTO { - id: string; - user_id: string; - access_token?: string; - friend_sync?: boolean; - name: string; - revoked?: boolean; - show_activity?: number; - type: string; - verified?: boolean; - visibility?: number; - integrations?: string[]; - metadata_?: unknown; - metadata_visibility?: number; - two_way_link?: boolean; + id: string; + user_id: string; + access_token?: string; + friend_sync?: boolean; + name: string; + revoked?: boolean; + show_activity?: number; + type: string; + verified?: boolean; + visibility?: number; + integrations?: string[]; + metadata_?: unknown; + metadata_visibility?: number; + two_way_link?: boolean; - constructor(connectedAccount: ConnectedAccount, with_token: boolean = false) { - this.id = connectedAccount.external_id; - this.user_id = connectedAccount.user_id; - this.access_token = connectedAccount.token_data && with_token ? connectedAccount.token_data.access_token : undefined; - this.friend_sync = connectedAccount.friend_sync; - this.name = connectedAccount.name; - this.revoked = connectedAccount.revoked; - this.show_activity = connectedAccount.show_activity; - this.type = connectedAccount.type; - this.verified = connectedAccount.verified; - this.visibility = +(connectedAccount.visibility || false); - this.integrations = connectedAccount.integrations; - this.metadata_ = connectedAccount.metadata_; - this.metadata_visibility = +(connectedAccount.metadata_visibility || false); - this.two_way_link = connectedAccount.two_way_link; - } + constructor(connectedAccount: ConnectedAccount, with_token: boolean = false) { + this.id = connectedAccount.external_id; + this.user_id = connectedAccount.user_id; + this.access_token = connectedAccount.token_data && with_token ? connectedAccount.token_data.access_token : undefined; + this.friend_sync = connectedAccount.friend_sync; + this.name = connectedAccount.name; + this.revoked = connectedAccount.revoked; + this.show_activity = connectedAccount.show_activity; + this.type = connectedAccount.type; + this.verified = connectedAccount.verified; + this.visibility = +(connectedAccount.visibility || false); + this.integrations = connectedAccount.integrations; + this.metadata_ = connectedAccount.metadata_; + this.metadata_visibility = +(connectedAccount.metadata_visibility || false); + this.two_way_link = connectedAccount.two_way_link; + } } diff --git a/src/util/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts index f72047a..5ab8ec4 100644 --- a/src/util/dtos/DmChannelDTO.ts +++ b/src/util/dtos/DmChannelDTO.ts @@ -21,43 +21,43 @@ import { PublicUserProjection } from "@spacebar/schemas"; export class DmChannelDTO { - icon: string | null; - id: string; - last_message_id: string | null; - name: string | null; - origin_channel_id: string | null; - owner_id?: string; - recipients: MinimalPublicUserDTO[]; - type: number; + icon: string | null; + id: string; + last_message_id: string | null; + name: string | null; + origin_channel_id: string | null; + owner_id?: string; + recipients: MinimalPublicUserDTO[]; + type: number; - static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) { - const obj = new DmChannelDTO(); - obj.icon = channel.icon || null; - obj.id = channel.id; - obj.last_message_id = channel.last_message_id || null; - obj.name = channel.name || null; - obj.origin_channel_id = origin_channel_id || null; - obj.owner_id = channel.owner_id; - obj.type = channel.type; - obj.recipients = ( - await Promise.all( - channel.recipients - ?.filter((r) => !excluded_recipients.includes(r.user_id)) - .map(async (r) => { - return await User.findOneOrFail({ - where: { id: r.user_id }, - select: PublicUserProjection, - }); - }) || [], - ) - ).map((u) => new MinimalPublicUserDTO(u)); - return obj; - } + static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) { + const obj = new DmChannelDTO(); + obj.icon = channel.icon || null; + obj.id = channel.id; + obj.last_message_id = channel.last_message_id || null; + obj.name = channel.name || null; + obj.origin_channel_id = origin_channel_id || null; + obj.owner_id = channel.owner_id; + obj.type = channel.type; + obj.recipients = ( + await Promise.all( + channel.recipients + ?.filter((r) => !excluded_recipients.includes(r.user_id)) + .map(async (r) => { + return await User.findOneOrFail({ + where: { id: r.user_id }, + select: PublicUserProjection, + }); + }) || [], + ) + ).map((u) => new MinimalPublicUserDTO(u)); + return obj; + } - excludedRecipients(excluded_recipients: string[]): DmChannelDTO { - return { - ...this, - recipients: this.recipients.filter((r) => !excluded_recipients.includes(r.id)), - }; - } + excludedRecipients(excluded_recipients: string[]): DmChannelDTO { + return { + ...this, + recipients: this.recipients.filter((r) => !excluded_recipients.includes(r.id)), + }; + } } diff --git a/src/util/dtos/ReadyGuildDTO.ts b/src/util/dtos/ReadyGuildDTO.ts index d24b68e..3ea9f1b 100644 --- a/src/util/dtos/ReadyGuildDTO.ts +++ b/src/util/dtos/ReadyGuildDTO.ts @@ -21,207 +21,207 @@ // TODO: this is not the best place for this type export type ReadyUserGuildSettingsEntries = Omit & { - channel_overrides: (ChannelOverride & { channel_id: string })[]; + channel_overrides: (ChannelOverride & { channel_id: string })[]; }; // TODO: probably should move somewhere else export interface ReadyPrivateChannel { - id: string; - flags: number; - is_spam: boolean; - last_message_id?: string; - recipients: PublicUser[]; - type: ChannelType.DM | ChannelType.GROUP_DM; + id: string; + flags: number; + is_spam: boolean; + last_message_id?: string; + recipients: PublicUser[]; + type: ChannelType.DM | ChannelType.GROUP_DM; } export type GuildOrUnavailable = { id: string; unavailable: boolean } | (Guild & { joined_at?: Date; unavailable: undefined }); const guildIsAvailable = (guild: GuildOrUnavailable): guild is Guild & { joined_at: Date; unavailable: false } => { - return guild.unavailable != true; + return guild.unavailable != true; }; export interface IReadyGuildDTO { - application_command_counts?: { 1: number; 2: number; 3: number }; // ???????????? - channels: Channel[]; - data_mode: string; // what is this - emojis: Emoji[]; - guild_scheduled_events: unknown[]; // TODO - id: string; - large: boolean | undefined; - lazy: boolean; - member_count: number | undefined; - members: PublicMember[]; - premium_subscription_count: number | undefined; - properties: { - name: string; - description?: string | null; - icon?: string | null; - splash?: string | null; - banner?: string | null; - features: string[]; - preferred_locale?: string | null; - owner_id?: string | null; - application_id?: string | null; - afk_channel_id?: string | null; - afk_timeout: number | undefined; - system_channel_id?: string | null; - verification_level: number | undefined; - explicit_content_filter: number | undefined; - default_message_notifications: number | undefined; - mfa_level: number | undefined; - vanity_url_code?: string | null; - premium_tier: number | undefined; - premium_progress_bar_enabled: boolean; - system_channel_flags: number | undefined; - discovery_splash?: string | null; - rules_channel_id?: string | null; - public_updates_channel_id?: string | null; - max_video_channel_users: number | undefined; - max_members: number | undefined; - nsfw_level: number | undefined; - hub_type?: unknown | null; // ???? + application_command_counts?: { 1: number; 2: number; 3: number }; // ???????????? + channels: Channel[]; + data_mode: string; // what is this + emojis: Emoji[]; + guild_scheduled_events: unknown[]; // TODO + id: string; + large: boolean | undefined; + lazy: boolean; + member_count: number | undefined; + members: PublicMember[]; + premium_subscription_count: number | undefined; + properties: { + name: string; + description?: string | null; + icon?: string | null; + splash?: string | null; + banner?: string | null; + features: string[]; + preferred_locale?: string | null; + owner_id?: string | null; + application_id?: string | null; + afk_channel_id?: string | null; + afk_timeout: number | undefined; + system_channel_id?: string | null; + verification_level: number | undefined; + explicit_content_filter: number | undefined; + default_message_notifications: number | undefined; + mfa_level: number | undefined; + vanity_url_code?: string | null; + premium_tier: number | undefined; + premium_progress_bar_enabled: boolean; + system_channel_flags: number | undefined; + discovery_splash?: string | null; + rules_channel_id?: string | null; + public_updates_channel_id?: string | null; + max_video_channel_users: number | undefined; + max_members: number | undefined; + nsfw_level: number | undefined; + hub_type?: unknown | null; // ???? - home_header: null; // TODO - latest_onboarding_question_id: null; // TODO - safety_alerts_channel_id: null; // TODO - max_stage_video_channel_users: 50; // TODO - nsfw: boolean; - id: string; - }; - roles: Role[]; - stage_instances: unknown[]; - stickers: Sticker[]; - threads: unknown[]; - version: string; - guild_hashes: unknown; - unavailable: boolean; + home_header: null; // TODO + latest_onboarding_question_id: null; // TODO + safety_alerts_channel_id: null; // TODO + max_stage_video_channel_users: 50; // TODO + nsfw: boolean; + id: string; + }; + roles: Role[]; + stage_instances: unknown[]; + stickers: Sticker[]; + threads: unknown[]; + version: string; + guild_hashes: unknown; + unavailable: boolean; } export class ReadyGuildDTO implements IReadyGuildDTO { - application_command_counts?: { 1: number; 2: number; 3: number }; // ???????????? - channels: Channel[]; - data_mode: string; // what is this - emojis: Emoji[]; - guild_scheduled_events: unknown[]; - id: string; - large: boolean | undefined; - lazy: boolean; - member_count: number | undefined; - members: PublicMember[]; - premium_subscription_count: number | undefined; - properties: { - name: string; - description?: string | null; - icon?: string | null; - splash?: string | null; - banner?: string | null; - features: string[]; - preferred_locale?: string | null; - owner_id?: string | null; - application_id?: string | null; - afk_channel_id?: string | null; - afk_timeout: number | undefined; - system_channel_id?: string | null; - verification_level: number | undefined; - explicit_content_filter: number | undefined; - default_message_notifications: number | undefined; - mfa_level: number | undefined; - vanity_url_code?: string | null; - premium_tier: number | undefined; - premium_progress_bar_enabled: boolean; - system_channel_flags: number | undefined; - discovery_splash?: string | null; - rules_channel_id?: string | null; - public_updates_channel_id?: string | null; - max_video_channel_users: number | undefined; - max_members: number | undefined; - nsfw_level: number | undefined; - hub_type?: unknown | null; // ???? + application_command_counts?: { 1: number; 2: number; 3: number }; // ???????????? + channels: Channel[]; + data_mode: string; // what is this + emojis: Emoji[]; + guild_scheduled_events: unknown[]; + id: string; + large: boolean | undefined; + lazy: boolean; + member_count: number | undefined; + members: PublicMember[]; + premium_subscription_count: number | undefined; + properties: { + name: string; + description?: string | null; + icon?: string | null; + splash?: string | null; + banner?: string | null; + features: string[]; + preferred_locale?: string | null; + owner_id?: string | null; + application_id?: string | null; + afk_channel_id?: string | null; + afk_timeout: number | undefined; + system_channel_id?: string | null; + verification_level: number | undefined; + explicit_content_filter: number | undefined; + default_message_notifications: number | undefined; + mfa_level: number | undefined; + vanity_url_code?: string | null; + premium_tier: number | undefined; + premium_progress_bar_enabled: boolean; + system_channel_flags: number | undefined; + discovery_splash?: string | null; + rules_channel_id?: string | null; + public_updates_channel_id?: string | null; + max_video_channel_users: number | undefined; + max_members: number | undefined; + nsfw_level: number | undefined; + hub_type?: unknown | null; // ???? - home_header: null; // TODO - latest_onboarding_question_id: null; // TODO - safety_alerts_channel_id: null; // TODO - max_stage_video_channel_users: 50; // TODO - nsfw: boolean; - id: string; - }; - roles: Role[]; - stage_instances: unknown[]; - stickers: Sticker[]; - threads: unknown[]; - version: string; - guild_hashes: unknown; - unavailable: boolean; - joined_at: Date; + home_header: null; // TODO + latest_onboarding_question_id: null; // TODO + safety_alerts_channel_id: null; // TODO + max_stage_video_channel_users: 50; // TODO + nsfw: boolean; + id: string; + }; + roles: Role[]; + stage_instances: unknown[]; + stickers: Sticker[]; + threads: unknown[]; + version: string; + guild_hashes: unknown; + unavailable: boolean; + joined_at: Date; - constructor(guild: GuildOrUnavailable) { - if (!guildIsAvailable(guild)) { - this.id = guild.id; - this.unavailable = true; - return; - } + constructor(guild: GuildOrUnavailable) { + if (!guildIsAvailable(guild)) { + this.id = guild.id; + this.unavailable = true; + return; + } - this.application_command_counts = { - 1: 5, - 2: 2, - 3: 2, - }; // ????? // emma: this appears to always be an empty attrset... - this.channels = guild.channels; - this.data_mode = "full"; - this.emojis = guild.emojis; - this.guild_scheduled_events = []; - this.id = guild.id; - this.large = guild.large; - this.lazy = true; // ?????????? - this.member_count = guild.member_count; - this.members = guild.members?.map((x) => x.toPublicMember()); - this.premium_subscription_count = guild.premium_subscription_count; - this.properties = { - name: guild.name, - description: guild.description, - icon: guild.icon, - splash: guild.splash, - banner: guild.banner, - features: guild.features, - preferred_locale: guild.preferred_locale, - owner_id: guild.owner_id, - application_id: null, // ????? - afk_channel_id: guild.afk_channel_id, - afk_timeout: guild.afk_timeout, - system_channel_id: guild.system_channel_id, - verification_level: guild.verification_level, - explicit_content_filter: guild.explicit_content_filter, - default_message_notifications: guild.default_message_notifications, - mfa_level: guild.mfa_level, - vanity_url_code: null, // ????? - premium_tier: guild.premium_tier, - premium_progress_bar_enabled: guild.premium_progress_bar_enabled, - system_channel_flags: guild.system_channel_flags, - discovery_splash: guild.discovery_splash, - rules_channel_id: guild.rules_channel_id, - public_updates_channel_id: guild.public_updates_channel_id, - max_video_channel_users: guild.max_video_channel_users, - max_members: guild.max_members, - nsfw_level: guild.nsfw_level, - hub_type: null, + this.application_command_counts = { + 1: 5, + 2: 2, + 3: 2, + }; // ????? // emma: this appears to always be an empty attrset... + this.channels = guild.channels; + this.data_mode = "full"; + this.emojis = guild.emojis; + this.guild_scheduled_events = []; + this.id = guild.id; + this.large = guild.large; + this.lazy = true; // ?????????? + this.member_count = guild.member_count; + this.members = guild.members?.map((x) => x.toPublicMember()); + this.premium_subscription_count = guild.premium_subscription_count; + this.properties = { + name: guild.name, + description: guild.description, + icon: guild.icon, + splash: guild.splash, + banner: guild.banner, + features: guild.features, + preferred_locale: guild.preferred_locale, + owner_id: guild.owner_id, + application_id: null, // ????? + afk_channel_id: guild.afk_channel_id, + afk_timeout: guild.afk_timeout, + system_channel_id: guild.system_channel_id, + verification_level: guild.verification_level, + explicit_content_filter: guild.explicit_content_filter, + default_message_notifications: guild.default_message_notifications, + mfa_level: guild.mfa_level, + vanity_url_code: null, // ????? + premium_tier: guild.premium_tier, + premium_progress_bar_enabled: guild.premium_progress_bar_enabled, + system_channel_flags: guild.system_channel_flags, + discovery_splash: guild.discovery_splash, + rules_channel_id: guild.rules_channel_id, + public_updates_channel_id: guild.public_updates_channel_id, + max_video_channel_users: guild.max_video_channel_users, + max_members: guild.max_members, + nsfw_level: guild.nsfw_level, + hub_type: null, - home_header: null, - id: guild.id, - latest_onboarding_question_id: null, - max_stage_video_channel_users: 50, // TODO - nsfw: guild.nsfw, - safety_alerts_channel_id: null, - }; - this.roles = guild.roles.map((x) => x.toJSON()); - this.stage_instances = []; - this.stickers = guild.stickers; - this.threads = []; - this.version = "1"; // ?????? - this.guild_hashes = {}; - this.joined_at = guild.joined_at; - } + home_header: null, + id: guild.id, + latest_onboarding_question_id: null, + max_stage_video_channel_users: 50, // TODO + nsfw: guild.nsfw, + safety_alerts_channel_id: null, + }; + this.roles = guild.roles.map((x) => x.toJSON()); + this.stage_instances = []; + this.stickers = guild.stickers; + this.threads = []; + this.version = "1"; // ?????? + this.guild_hashes = {}; + this.joined_at = guild.joined_at; + } - toJSON() { - return this as IReadyGuildDTO; - } + toJSON() { + return this as IReadyGuildDTO; + } } diff --git a/src/util/dtos/UserDTO.ts b/src/util/dtos/UserDTO.ts index 17e4435..2a79f8f 100644 --- a/src/util/dtos/UserDTO.ts +++ b/src/util/dtos/UserDTO.ts @@ -19,19 +19,19 @@ import { User } from "../entities"; export class MinimalPublicUserDTO { - avatar?: string | null; - discriminator: string; - id: string; - public_flags: number; - username: string; - badge_ids?: string[] | null; + avatar?: string | null; + discriminator: string; + id: string; + public_flags: number; + username: string; + badge_ids?: string[] | null; - constructor(user: User) { - this.avatar = user.avatar; - this.discriminator = user.discriminator; - this.id = user.id; - this.public_flags = user.public_flags; - this.username = user.username; - this.badge_ids = user.badge_ids; - } + constructor(user: User) { + this.avatar = user.avatar; + this.discriminator = user.discriminator; + this.id = user.id; + this.public_flags = user.public_flags; + this.username = user.username; + this.badge_ids = user.badge_ids; + } } diff --git a/src/util/entities/Application.ts b/src/util/entities/Application.ts index 8ebe6ce..e00b4a9 100644 --- a/src/util/entities/Application.ts +++ b/src/util/entities/Application.ts @@ -23,116 +23,116 @@ import { Guild } from "./Guild"; @Entity({ - name: "applications", + name: "applications", }) export class Application extends BaseClass { - @Column() - name: string; + @Column() + name: string; - @Column({ nullable: true }) - icon?: string; + @Column({ nullable: true }) + icon?: string; - @Column({ nullable: true }) - description: string; + @Column({ nullable: true }) + description: string; - @Column({ nullable: true }) - summary: string = ""; + @Column({ nullable: true }) + summary: string = ""; - @Column({ type: "simple-json", nullable: true }) - type?: object; // TODO: this type is bad + @Column({ type: "simple-json", nullable: true }) + type?: object; // TODO: this type is bad - @Column() - hook: boolean = true; + @Column() + hook: boolean = true; - @Column() - bot_public?: boolean = true; + @Column() + bot_public?: boolean = true; - @Column() - bot_require_code_grant?: boolean = false; + @Column() + bot_require_code_grant?: boolean = false; - @Column() - verify_key: string; + @Column() + verify_key: string; - @JoinColumn({ name: "owner_id" }) - @ManyToOne(() => User, { onDelete: "CASCADE" }) - owner: User; + @JoinColumn({ name: "owner_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + owner: User; - // TODO: enum this? https://discord.com/developers/docs/resources/application#application-object-application-flags - @Column() - flags: number = 0; + // TODO: enum this? https://discord.com/developers/docs/resources/application#application-object-application-flags + @Column() + flags: number = 0; - @Column({ type: "simple-array", nullable: true }) - redirect_uris: string[] = []; + @Column({ type: "simple-array", nullable: true }) + redirect_uris: string[] = []; - @Column({ nullable: true }) - rpc_application_state: number = 0; + @Column({ nullable: true }) + rpc_application_state: number = 0; - @Column({ nullable: true }) - store_application_state: number = 1; + @Column({ nullable: true }) + store_application_state: number = 1; - @Column({ nullable: true }) - verification_state: number = 1; + @Column({ nullable: true }) + verification_state: number = 1; - @Column({ nullable: true }) - interactions_endpoint_url?: string; + @Column({ nullable: true }) + interactions_endpoint_url?: string; - @Column({ nullable: true }) - integration_public: boolean = true; + @Column({ nullable: true }) + integration_public: boolean = true; - @Column({ nullable: true }) - integration_require_code_grant: boolean = false; + @Column({ nullable: true }) + integration_require_code_grant: boolean = false; - @Column({ nullable: true }) - discoverability_state: number = 1; + @Column({ nullable: true }) + discoverability_state: number = 1; - @Column({ nullable: true }) - discovery_eligibility_flags: number = 2240; + @Column({ nullable: true }) + discovery_eligibility_flags: number = 2240; - @JoinColumn({ name: "bot_user_id" }) - @OneToOne(() => User, { onDelete: "CASCADE" }) - bot?: User; + @JoinColumn({ name: "bot_user_id" }) + @OneToOne(() => User, { onDelete: "CASCADE" }) + bot?: User; - @Column({ type: "simple-array", nullable: true }) - tags?: string[]; + @Column({ type: "simple-array", nullable: true }) + tags?: string[]; - @Column({ nullable: true }) - cover_image?: string; // the application's default rich presence invite cover image hash + @Column({ nullable: true }) + cover_image?: string; // the application's default rich presence invite cover image hash - @Column({ type: "simple-json", nullable: true }) - install_params?: { scopes: string[]; permissions: string }; + @Column({ type: "simple-json", nullable: true }) + install_params?: { scopes: string[]; permissions: string }; - @Column({ nullable: true }) - terms_of_service_url?: string; + @Column({ nullable: true }) + terms_of_service_url?: string; - @Column({ nullable: true }) - privacy_policy_url?: string; + @Column({ nullable: true }) + privacy_policy_url?: string; - @Column({ nullable: true }) - @RelationId((application: Application) => application.guild) - guild_id?: string; + @Column({ nullable: true }) + @RelationId((application: Application) => application.guild) + guild_id?: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) - guild?: Guild; // guild to which the app is linked, e.g. a developer support server + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild) + guild?: Guild; // guild to which the app is linked, e.g. a developer support server - @Column({ nullable: true }) - custom_install_url?: string; + @Column({ nullable: true }) + custom_install_url?: string; - //just for us + //just for us - //@Column({ type: "simple-array", nullable: true }) - //rpc_origins?: string[]; + //@Column({ type: "simple-array", nullable: true }) + //rpc_origins?: string[]; - //@Column({ nullable: true }) - //primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created, + //@Column({ nullable: true }) + //primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created, - //@Column({ nullable: true }) - //slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page + //@Column({ nullable: true }) + //slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page - @JoinColumn({ name: "team_id" }) - @ManyToOne(() => Team, { - onDelete: "CASCADE", - nullable: true, - }) - team?: Team; + @JoinColumn({ name: "team_id" }) + @ManyToOne(() => Team, { + onDelete: "CASCADE", + nullable: true, + }) + team?: Team; } diff --git a/src/util/entities/ApplicationCommand.ts b/src/util/entities/ApplicationCommand.ts index 8ec154f..21153d8 100644 --- a/src/util/entities/ApplicationCommand.ts +++ b/src/util/entities/ApplicationCommand.ts @@ -19,70 +19,70 @@ import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; import { - ApplicationCommandHandlerType, - ApplicationCommandOption, - ApplicationCommandIndexPermissions, - ApplicationCommandType, - Snowflake, - ApplicationIntegrationType, - InteractionContextType, + ApplicationCommandHandlerType, + ApplicationCommandOption, + ApplicationCommandIndexPermissions, + ApplicationCommandType, + Snowflake, + ApplicationIntegrationType, + InteractionContextType, } from "@spacebar/schemas"; @Entity({ - name: "application_commands", + name: "application_commands", }) export class ApplicationCommand extends BaseClass { - @Column({ default: ApplicationCommandType.CHAT_INPUT }) - type?: ApplicationCommandType; + @Column({ default: ApplicationCommandType.CHAT_INPUT }) + type?: ApplicationCommandType; - @Column() - application_id: Snowflake; + @Column() + application_id: Snowflake; - @Column({ nullable: true }) - guild_id?: Snowflake; + @Column({ nullable: true }) + guild_id?: Snowflake; - @Column() - name: string; + @Column() + name: string; - @Column({ nullable: true, type: "simple-json" }) - name_localizations?: Record; + @Column({ nullable: true, type: "simple-json" }) + name_localizations?: Record; - @Column() - description: string; + @Column() + description: string; - @Column({ nullable: true, type: "simple-json" }) - description_localizations?: Record; + @Column({ nullable: true, type: "simple-json" }) + description_localizations?: Record; - @Column({ type: "simple-json", default: "[]" }) - options: ApplicationCommandOption[]; + @Column({ type: "simple-json", default: "[]" }) + options: ApplicationCommandOption[]; - @Column({ nullable: true, type: String }) - default_member_permissions: string | null; + @Column({ nullable: true, type: String }) + default_member_permissions: string | null; - /* - * @deprecated - */ - @Column({ default: true }) - dm_permission?: boolean; + /* + * @deprecated + */ + @Column({ default: true }) + dm_permission?: boolean; - @Column({ nullable: true, type: "simple-json" }) - permissions?: ApplicationCommandIndexPermissions; + @Column({ nullable: true, type: "simple-json" }) + permissions?: ApplicationCommandIndexPermissions; - @Column({ default: false }) - nsfw?: boolean; + @Column({ default: false }) + nsfw?: boolean; - @Column({ nullable: true, type: "simple-json" }) - integration_types?: ApplicationIntegrationType[]; + @Column({ nullable: true, type: "simple-json" }) + integration_types?: ApplicationIntegrationType[]; - @Column({ default: 0 }) - global_popularity_rank?: number; + @Column({ default: 0 }) + global_popularity_rank?: number; - @Column({ nullable: true, type: "simple-json" }) - contexts?: InteractionContextType[]; + @Column({ nullable: true, type: "simple-json" }) + contexts?: InteractionContextType[]; - @Column({ default: 0 }) - version: Snowflake; + @Column({ default: 0 }) + version: Snowflake; - @Column({ default: 0 }) - handler?: ApplicationCommandHandlerType; + @Column({ default: 0 }) + handler?: ApplicationCommandHandlerType; } diff --git a/src/util/entities/Attachment.ts b/src/util/entities/Attachment.ts index 5232922..c6858eb 100644 --- a/src/util/entities/Attachment.ts +++ b/src/util/entities/Attachment.ts @@ -23,54 +23,54 @@ import { getUrlSignature, NewUrlUserSignatureData, NewUrlSignatureData } from "../Signing"; @Entity({ - name: "attachments", + name: "attachments", }) export class Attachment extends BaseClass { - @Column() - filename: string; // name of file attached + @Column() + filename: string; // name of file attached - @Column() - size: number; // size of file in bytes + @Column() + size: number; // size of file in bytes - @Column() - url: string; // source url of file + @Column() + url: string; // source url of file - @Column() - proxy_url: string; // a proxied url of file + @Column() + proxy_url: string; // a proxied url of file - @Column({ nullable: true }) - height?: number; // height of file (if image) + @Column({ nullable: true }) + height?: number; // height of file (if image) - @Column({ nullable: true }) - width?: number; // width of file (if image) + @Column({ nullable: true }) + width?: number; // width of file (if image) - @Column({ nullable: true }) - content_type?: string; + @Column({ nullable: true }) + content_type?: string; - @Column({ nullable: true }) - @RelationId((attachment: Attachment) => attachment.message) - message_id: string; + @Column({ nullable: true }) + @RelationId((attachment: Attachment) => attachment.message) + message_id: string; - @JoinColumn({ name: "message_id" }) - @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, { - onDelete: "CASCADE", - }) - message: import("./Message").Message; + @JoinColumn({ name: "message_id" }) + @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, { + onDelete: "CASCADE", + }) + message: import("./Message").Message; - @BeforeRemove() - onDelete() { - return deleteFile(new URL(this.url).pathname); - } + @BeforeRemove() + onDelete() { + return deleteFile(new URL(this.url).pathname); + } - signUrls(data: NewUrlUserSignatureData): Attachment { - return { - ...this, - url: getUrlSignature(new NewUrlSignatureData({ ...data, url: this.url })) - .applyToUrl(this.url) - .toString(), - proxy_url: getUrlSignature(new NewUrlSignatureData({ ...data, url: this.proxy_url })) - .applyToUrl(this.proxy_url) - .toString(), - }; - } + signUrls(data: NewUrlUserSignatureData): Attachment { + return { + ...this, + url: getUrlSignature(new NewUrlSignatureData({ ...data, url: this.url })) + .applyToUrl(this.url) + .toString(), + proxy_url: getUrlSignature(new NewUrlSignatureData({ ...data, url: this.proxy_url })) + .applyToUrl(this.proxy_url) + .toString(), + }; + } } diff --git a/src/util/entities/AuditLog.ts b/src/util/entities/AuditLog.ts index d21a3e0..c59d50a 100644 --- a/src/util/entities/AuditLog.ts +++ b/src/util/entities/AuditLog.ts @@ -22,40 +22,40 @@ import { AuditLogChange, AuditLogEvents } from "@spacebar/schemas"; @Entity({ - name: "audit_logs", + name: "audit_logs", }) export class AuditLog extends BaseClass { - @JoinColumn({ name: "target_id" }) - @ManyToOne(() => User) - target?: User; + @JoinColumn({ name: "target_id" }) + @ManyToOne(() => User) + target?: User; - @Column({ nullable: true }) - @RelationId((auditlog: AuditLog) => auditlog.user) - user_id: string; + @Column({ nullable: true }) + @RelationId((auditlog: AuditLog) => auditlog.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, (user: User) => user.id) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, (user: User) => user.id) + user: User; - @Column({ type: "int" }) - action_type: AuditLogEvents; + @Column({ type: "int" }) + action_type: AuditLogEvents; - @Column({ type: "simple-json", nullable: true }) - options?: { - delete_member_days?: string; - members_removed?: string; - channel_id?: string; - messaged_id?: string; - count?: string; - id?: string; - type?: string; - role_name?: string; - }; + @Column({ type: "simple-json", nullable: true }) + options?: { + delete_member_days?: string; + members_removed?: string; + channel_id?: string; + messaged_id?: string; + count?: string; + id?: string; + type?: string; + role_name?: string; + }; - @Column() - @Column({ type: "simple-json" }) - changes: AuditLogChange[]; + @Column() + @Column({ type: "simple-json" }) + changes: AuditLogChange[]; - @Column({ nullable: true }) - reason?: string; + @Column({ nullable: true }) + reason?: string; } diff --git a/src/util/entities/AutomodRule.ts b/src/util/entities/AutomodRule.ts index a1eeab2..ac7f330 100644 --- a/src/util/entities/AutomodRule.ts +++ b/src/util/entities/AutomodRule.ts @@ -22,46 +22,46 @@ import { AutomodAction, AutomodRuleActionType, AutomodRuleEventType, AutomodRuleTriggerMetadata, AutomodRuleTriggerType } from "@spacebar/schemas"; @Entity({ - name: "automod_rules", + name: "automod_rules", }) export class AutomodRule extends BaseClass { - @JoinColumn({ name: "creator_id" }) - @ManyToOne(() => User, { onDelete: "CASCADE" }) - creator: User; + @JoinColumn({ name: "creator_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + creator: User; - @Column() - enabled: boolean; + @Column() + enabled: boolean; - @Column() - event_type: AutomodRuleEventType; + @Column() + event_type: AutomodRuleEventType; - @Column({ type: "simple-array" }) - exempt_channels: string[]; + @Column({ type: "simple-array" }) + exempt_channels: string[]; - @Column({ type: "simple-array" }) - exempt_roles: string[]; + @Column({ type: "simple-array" }) + exempt_roles: string[]; - @Column() - guild_id: string; + @Column() + guild_id: string; - @Column() - name: string; + @Column() + name: string; - @Column() - position: number; + @Column() + position: number; - @Column() - trigger_type: AutomodRuleTriggerType; + @Column() + trigger_type: AutomodRuleTriggerType; - @Column({ - type: "simple-json", - nullable: true, - }) - trigger_metadata?: // this is null for "Block suspected spam content" - AutomodRuleTriggerMetadata; + @Column({ + type: "simple-json", + nullable: true, + }) + trigger_metadata?: // this is null for "Block suspected spam content" + AutomodRuleTriggerMetadata; - @Column({ - type: "simple-json", - }) - actions: AutomodAction[]; + @Column({ + type: "simple-json", + }) + actions: AutomodAction[]; } diff --git a/src/util/entities/BackupCodes.ts b/src/util/entities/BackupCodes.ts index a0c9ba4..2a55613 100644 --- a/src/util/entities/BackupCodes.ts +++ b/src/util/entities/BackupCodes.ts @@ -23,34 +23,34 @@ import { Config } from "../util"; @Entity({ - name: "backup_codes", + name: "backup_codes", }) export class BackupCode extends BaseClass { - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { onDelete: "CASCADE" }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + user: User; - @Column() - code: string; + @Column() + code: string; - @Column() - consumed: boolean; + @Column() + consumed: boolean; - @Column() - expired: boolean; + @Column() + expired: boolean; } export function generateMfaBackupCodes(user_id: string) { - const backup_codes: BackupCode[] = []; - for (let i = 0; i < Config.get().security.mfaBackupCodeCount; i++) { - const code = BackupCode.create({ - user: { id: user_id }, - code: crypto.randomBytes(4).toString("hex"), // 8 characters - consumed: false, - expired: false, - }); - backup_codes.push(code); - } + const backup_codes: BackupCode[] = []; + for (let i = 0; i < Config.get().security.mfaBackupCodeCount; i++) { + const code = BackupCode.create({ + user: { id: user_id }, + code: crypto.randomBytes(4).toString("hex"), // 8 characters + consumed: false, + expired: false, + }); + backup_codes.push(code); + } - return backup_codes; + return backup_codes; } diff --git a/src/util/entities/Badge.ts b/src/util/entities/Badge.ts index 38489e8..924ef79 100644 --- a/src/util/entities/Badge.ts +++ b/src/util/entities/Badge.ts @@ -20,18 +20,18 @@ import { BaseClassWithoutId } from "./BaseClass"; @Entity({ - name: "badges", + name: "badges", }) export class Badge extends BaseClassWithoutId { - @Column({ primary: true }) - id: string; + @Column({ primary: true }) + id: string; - @Column() - description: string; + @Column() + description: string; - @Column() - icon: string; + @Column() + icon: string; - @Column({ nullable: true }) - link?: string; + @Column({ nullable: true }) + link?: string; } diff --git a/src/util/entities/Ban.ts b/src/util/entities/Ban.ts index c3c473f..17121ac 100644 --- a/src/util/entities/Ban.ts +++ b/src/util/entities/Ban.ts @@ -22,40 +22,40 @@ import { User } from "./User"; @Entity({ - name: "bans", + name: "bans", }) export class Ban extends BaseClass { - @Column({ nullable: true }) - @RelationId((ban: Ban) => ban.user) - user_id: string; + @Column({ nullable: true }) + @RelationId((ban: Ban) => ban.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - @Column({ nullable: true }) - @RelationId((ban: Ban) => ban.guild) - guild_id: string; + @Column({ nullable: true }) + @RelationId((ban: Ban) => ban.guild) + guild_id: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { - onDelete: "CASCADE", - }) - guild: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) + guild: Guild; - @Column({ nullable: true }) - @RelationId((ban: Ban) => ban.executor) - executor_id: string; + @Column({ nullable: true }) + @RelationId((ban: Ban) => ban.executor) + executor_id: string; - @JoinColumn({ name: "executor_id" }) - @ManyToOne(() => User) - executor: User; + @JoinColumn({ name: "executor_id" }) + @ManyToOne(() => User) + executor: User; - @Column({ nullable: true }) - ip?: string; + @Column({ nullable: true }) + ip?: string; - @Column({ nullable: true }) - reason?: string; + @Column({ nullable: true }) + reason?: string; } diff --git a/src/util/entities/BaseClass.ts b/src/util/entities/BaseClass.ts index 9fad798..f30584e 100644 --- a/src/util/entities/BaseClass.ts +++ b/src/util/entities/BaseClass.ts @@ -22,59 +22,59 @@ import { OrmUtils } from "../imports/OrmUtils"; export class BaseClassWithoutId extends BaseEntity { - private get construct() { - return this.constructor; - } + private get construct() { + return this.constructor; + } - private get metadata() { - return getDatabase()?.getMetadata(this.construct); - } + private get metadata() { + return getDatabase()?.getMetadata(this.construct); + } - assign(props: object) { - OrmUtils.mergeDeep(this, props); - return this; - } + assign(props: object) { + OrmUtils.mergeDeep(this, props); + return this; + } - // TODO: fix eslint - // eslint-disable-next-line @typescript-eslint/no-explicit-any - toJSON(): any { - return Object.fromEntries( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - this.metadata!.columns // @ts-ignore - .map((x) => [x.propertyName, this[x.propertyName]]) - .concat( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this.metadata.relations.map((x) => [ - x.propertyName, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this[x.propertyName], - ]), - ), - ); - } + // TODO: fix eslint + // eslint-disable-next-line @typescript-eslint/no-explicit-any + toJSON(): any { + return Object.fromEntries( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + this.metadata!.columns // @ts-ignore + .map((x) => [x.propertyName, this[x.propertyName]]) + .concat( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.metadata.relations.map((x) => [ + x.propertyName, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this[x.propertyName], + ]), + ), + ); + } - static increment(conditions: FindOptionsWhere, propertyPath: string, value: number | string) { - const repository = this.getRepository(); - return repository.increment(conditions, propertyPath, value); - } + static increment(conditions: FindOptionsWhere, propertyPath: string, value: number | string) { + const repository = this.getRepository(); + return repository.increment(conditions, propertyPath, value); + } - static decrement(conditions: FindOptionsWhere, propertyPath: string, value: number | string) { - const repository = this.getRepository(); - return repository.decrement(conditions, propertyPath, value); - } + static decrement(conditions: FindOptionsWhere, propertyPath: string, value: number | string) { + const repository = this.getRepository(); + return repository.decrement(conditions, propertyPath, value); + } } export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn; export class BaseClass extends BaseClassWithoutId { - @PrimaryIdColumn() - id: string = Snowflake.generate(); + @PrimaryIdColumn() + id: string = Snowflake.generate(); - @BeforeUpdate() - @BeforeInsert() - _do_validate() { - if (!this.id) this.id = Snowflake.generate(); - } + @BeforeUpdate() + @BeforeInsert() + _do_validate() { + if (!this.id) this.id = Snowflake.generate(); + } } diff --git a/src/util/entities/Categories.ts b/src/util/entities/Categories.ts index 6221f62..8320186 100644 --- a/src/util/entities/Categories.ts +++ b/src/util/entities/Categories.ts @@ -34,24 +34,24 @@ // Also populate discord default categories @Entity({ - name: "categories", + name: "categories", }) export class Categories extends BaseClassWithoutId { - // Not using snowflake + // Not using snowflake - @PrimaryIdColumn() - id: number; + @PrimaryIdColumn() + id: number; - @Column({ nullable: true }) - name: string; + @Column({ nullable: true }) + name: string; - @Column({ type: "simple-json" }) - localizations: string; + @Column({ type: "simple-json" }) + localizations: string; - // Whether to show the category prominently (e.g. in a sidebar) instead of only secondary (e.g. in search results) - @Column({ nullable: true }) - is_primary: boolean; + // Whether to show the category prominently (e.g. in a sidebar) instead of only secondary (e.g. in search results) + @Column({ nullable: true }) + is_primary: boolean; - @Column({ nullable: true }) - icon?: string; + @Column({ nullable: true }) + icon?: string; } diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts index e21669e..8ec801c 100644 --- a/src/util/entities/Channel.ts +++ b/src/util/entities/Channel.ts @@ -34,230 +34,230 @@ import { ChannelPermissionOverwrite, ChannelPermissionOverwriteType, ChannelType, PublicUserProjection } from "@spacebar/schemas"; @Entity({ - name: "channels", + name: "channels", }) export class Channel extends BaseClass { - @Column() - created_at: Date; + @Column() + created_at: Date; - @Column({ nullable: true }) - name?: string; + @Column({ nullable: true }) + name?: string; - @Column({ type: "text", nullable: true }) - icon?: string | null; + @Column({ type: "text", nullable: true }) + icon?: string | null; - @Column({ type: "int" }) - type: ChannelType; + @Column({ type: "int" }) + type: ChannelType; - @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - recipients?: Recipient[]; + @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { + cascade: true, + orphanedRowAction: "delete", + }) + recipients?: Recipient[]; - @Column({ nullable: true }) - last_message_id?: string; + @Column({ nullable: true }) + last_message_id?: string; - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.guild) - guild_id?: string; + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.guild) + guild_id?: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild) => guild.channels, { - onDelete: "CASCADE", - nullable: true, - }) - guild?: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, (guild) => guild.channels, { + onDelete: "CASCADE", + nullable: true, + }) + guild?: Guild; - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.parent) - parent_id: string | null; + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.parent) + parent_id: string | null; - @JoinColumn({ name: "parent_id" }) - @ManyToOne(() => Channel) - parent?: Channel; + @JoinColumn({ name: "parent_id" }) + @ManyToOne(() => Channel) + parent?: Channel; - // for group DMs and owned custom channel types - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.owner) - owner_id?: string; + // for group DMs and owned custom channel types + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.owner) + owner_id?: string; - @JoinColumn({ name: "owner_id" }) - @ManyToOne(() => User) - owner: User; + @JoinColumn({ name: "owner_id" }) + @ManyToOne(() => User) + owner: User; - @Column({ nullable: true }) - last_pin_timestamp?: number; + @Column({ nullable: true }) + last_pin_timestamp?: number; - @Column({ nullable: true }) - default_auto_archive_duration?: number; + @Column({ nullable: true }) + default_auto_archive_duration?: number; - @Column({ type: "simple-json", nullable: true }) - permission_overwrites?: ChannelPermissionOverwrite[]; + @Column({ type: "simple-json", nullable: true }) + permission_overwrites?: ChannelPermissionOverwrite[]; - @Column({ nullable: true }) - video_quality_mode?: number; + @Column({ nullable: true }) + video_quality_mode?: number; - @Column({ nullable: true }) - bitrate?: number; + @Column({ nullable: true }) + bitrate?: number; - @Column({ nullable: true }) - user_limit?: number; + @Column({ nullable: true }) + user_limit?: number; - @Column() - nsfw: boolean = false; + @Column() + nsfw: boolean = false; - @Column({ nullable: true }) - rate_limit_per_user?: number; + @Column({ nullable: true }) + rate_limit_per_user?: number; - @Column({ nullable: true }) - topic?: string; + @Column({ nullable: true }) + topic?: string; - @OneToMany(() => Invite, (invite: Invite) => invite.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - invites?: Invite[]; + @OneToMany(() => Invite, (invite: Invite) => invite.channel, { + cascade: true, + orphanedRowAction: "delete", + }) + invites?: Invite[]; - @Column({ nullable: true }) - retention_policy_id?: string; + @Column({ nullable: true }) + retention_policy_id?: string; - @OneToMany(() => Message, (message: Message) => message.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - messages?: Message[]; + @OneToMany(() => Message, (message: Message) => message.channel, { + cascade: true, + orphanedRowAction: "delete", + }) + messages?: Message[]; - @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - voice_states?: VoiceState[]; + @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { + cascade: true, + orphanedRowAction: "delete", + }) + voice_states?: VoiceState[]; - @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - read_states?: ReadState[]; + @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { + cascade: true, + orphanedRowAction: "delete", + }) + read_states?: ReadState[]; - @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - webhooks?: Webhook[]; + @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { + cascade: true, + orphanedRowAction: "delete", + }) + webhooks?: Webhook[]; - @Column() - flags: number = 0; + @Column() + flags: number = 0; - @Column({ nullable: true }) - default_thread_rate_limit_per_user?: number = 0; + @Column({ nullable: true }) + default_thread_rate_limit_per_user?: number = 0; - /** Must be calculated Channel.calculatePosition */ - position: number; + /** Must be calculated Channel.calculatePosition */ + position: number; - // TODO: DM channel - static async createChannel( - channel: Partial, - user_id: string = "0", - opts?: { - keepId?: boolean; - skipExistsCheck?: boolean; - skipPermissionCheck?: boolean; - skipEventEmit?: boolean; - skipNameChecks?: boolean; - }, - ) { - if (!opts?.skipPermissionCheck) { - // Always check if user has permission first - const permissions = await getPermission(user_id, channel.guild_id); - permissions.hasThrow("MANAGE_CHANNELS"); - } + // TODO: DM channel + static async createChannel( + channel: Partial, + user_id: string = "0", + opts?: { + keepId?: boolean; + skipExistsCheck?: boolean; + skipPermissionCheck?: boolean; + skipEventEmit?: boolean; + skipNameChecks?: boolean; + }, + ) { + if (!opts?.skipPermissionCheck) { + // Always check if user has permission first + const permissions = await getPermission(user_id, channel.guild_id); + permissions.hasThrow("MANAGE_CHANNELS"); + } - const guild = await Guild.findOneOrFail({ - where: { id: channel.guild_id }, - select: { - features: !opts?.skipNameChecks, - channel_ordering: true, - id: true, - }, - }); + const guild = await Guild.findOneOrFail({ + where: { id: channel.guild_id }, + select: { + features: !opts?.skipNameChecks, + channel_ordering: true, + id: true, + }, + }); - if (!opts?.skipNameChecks) { - if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) { - for (const character of InvisibleCharacters) if (channel.name.includes(character)) throw new HTTPError("Channel name cannot include invalid characters", 403); + if (!opts?.skipNameChecks) { + if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) { + for (const character of InvisibleCharacters) if (channel.name.includes(character)) throw new HTTPError("Channel name cannot include invalid characters", 403); - // Categories skip these checks on discord.com - if (channel.type !== ChannelType.GUILD_CATEGORY || guild.features.includes("IRC_LIKE_CATEGORY_NAMES")) { - if (channel.name.includes(" ")) throw new HTTPError("Channel name cannot include invalid characters", 403); + // Categories skip these checks on discord.com + if (channel.type !== ChannelType.GUILD_CATEGORY || guild.features.includes("IRC_LIKE_CATEGORY_NAMES")) { + if (channel.name.includes(" ")) throw new HTTPError("Channel name cannot include invalid characters", 403); - if (channel.name.match(/--+/g)) throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403); + if (channel.name.match(/--+/g)) throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403); - if (channel.name.charAt(0) === "-" || channel.name.charAt(channel.name.length - 1) === "-") - throw new HTTPError("Channel name cannot start/end with dash.", 403); - } else channel.name = channel.name.trim(); //category names are trimmed client side on discord.com - } + if (channel.name.charAt(0) === "-" || channel.name.charAt(channel.name.length - 1) === "-") + throw new HTTPError("Channel name cannot start/end with dash.", 403); + } else channel.name = channel.name.trim(); //category names are trimmed client side on discord.com + } - if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) { - if (!channel.name) throw new HTTPError("Channel name cannot be empty.", 403); - } - } + if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) { + if (!channel.name) throw new HTTPError("Channel name cannot be empty.", 403); + } + } - switch (channel.type) { - case ChannelType.GUILD_TEXT: - case ChannelType.GUILD_NEWS: - case ChannelType.GUILD_VOICE: - if (channel.parent_id && !opts?.skipExistsCheck) { - const exists = await Channel.findOneOrFail({ - where: { id: channel.parent_id }, - }); - if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); - if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild"); - } - break; - case ChannelType.GUILD_CATEGORY: - case ChannelType.UNHANDLED: - break; - case ChannelType.DM: - case ChannelType.GROUP_DM: - throw new HTTPError("You can't create a dm channel in a guild"); - case ChannelType.GUILD_STORE: - default: - throw new HTTPError("Not yet supported"); - } + switch (channel.type) { + case ChannelType.GUILD_TEXT: + case ChannelType.GUILD_NEWS: + case ChannelType.GUILD_VOICE: + if (channel.parent_id && !opts?.skipExistsCheck) { + const exists = await Channel.findOneOrFail({ + where: { id: channel.parent_id }, + }); + if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); + if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild"); + } + break; + case ChannelType.GUILD_CATEGORY: + case ChannelType.UNHANDLED: + break; + case ChannelType.DM: + case ChannelType.GROUP_DM: + throw new HTTPError("You can't create a dm channel in a guild"); + case ChannelType.GUILD_STORE: + default: + throw new HTTPError("Not yet supported"); + } - if (!channel.permission_overwrites) channel.permission_overwrites = []; - // TODO: eagerly auto generate position of all guild channels + if (!channel.permission_overwrites) channel.permission_overwrites = []; + // TODO: eagerly auto generate position of all guild channels - const position = (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0; + const position = (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0; - channel = { - ...channel, - ...(!opts?.keepId && { id: Snowflake.generate() }), - created_at: new Date(), - position, - }; + channel = { + ...channel, + ...(!opts?.keepId && { id: Snowflake.generate() }), + created_at: new Date(), + position, + }; - const ret = Channel.create(channel); + const ret = Channel.create(channel); - await Promise.all([ - ret.save(), - !opts?.skipEventEmit - ? emitEvent({ - event: "CHANNEL_CREATE", - data: channel, - guild_id: channel.guild_id, - } as ChannelCreateEvent) - : Promise.resolve(), - Guild.insertChannelInOrder(guild.id, ret.id, position, guild), - ]); + await Promise.all([ + ret.save(), + !opts?.skipEventEmit + ? emitEvent({ + event: "CHANNEL_CREATE", + data: channel, + guild_id: channel.guild_id, + } as ChannelCreateEvent) + : Promise.resolve(), + Guild.insertChannelInOrder(guild.id, ret.id, position, guild), + ]); - return ret; - } + return ret; + } - static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { - recipients = [...new Set(recipients)].filter((x) => x !== creator_user_id); - // TODO: check config for max number of recipients - /** if you want to disallow note to self channels, uncomment the conditional below + static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { + recipients = [...new Set(recipients)].filter((x) => x !== creator_user_id); + // TODO: check config for max number of recipients + /** if you want to disallow note to self channels, uncomment the conditional below const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); if (otherRecipientsUsers.length !== recipients.length) { @@ -265,261 +265,261 @@ } **/ - const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM; + const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM; - let channel = null; + let channel = null; - const channelRecipients = [...recipients, creator_user_id]; + const channelRecipients = [...recipients, creator_user_id]; - const userRecipients = await Recipient.find({ - where: { user_id: creator_user_id }, - relations: ["channel", "channel.recipients"], - }); + const userRecipients = await Recipient.find({ + where: { user_id: creator_user_id }, + relations: ["channel", "channel.recipients"], + }); - for (const ur of userRecipients) { - if (!ur.channel.recipients) continue; - const re = ur.channel.recipients.map((r) => r.user_id); - if (re.length === channelRecipients.length) { - if (channelRecipients.every((_) => re.includes(_))) { - if (channel == null) { - channel = ur.channel; - await ur.assign({ closed: false }).save(); - } - } - } - } + for (const ur of userRecipients) { + if (!ur.channel.recipients) continue; + const re = ur.channel.recipients.map((r) => r.user_id); + if (re.length === channelRecipients.length) { + if (channelRecipients.every((_) => re.includes(_))) { + if (channel == null) { + channel = ur.channel; + await ur.assign({ closed: false }).save(); + } + } + } + } - if (channel == null) { - name = trimSpecial(name); + if (channel == null) { + name = trimSpecial(name); - channel = await Channel.create({ - name, - type, - owner_id: type === ChannelType.GROUP_DM ? creator_user_id : undefined, - created_at: new Date(), - last_message_id: undefined, - recipients: channelRecipients.map((x) => - Recipient.create({ - user_id: x, - closed: !(type === ChannelType.GROUP_DM || x === creator_user_id), - }), - ), - nsfw: false, - }).save(); - } + channel = await Channel.create({ + name, + type, + owner_id: type === ChannelType.GROUP_DM ? creator_user_id : undefined, + created_at: new Date(), + last_message_id: undefined, + recipients: channelRecipients.map((x) => + Recipient.create({ + user_id: x, + closed: !(type === ChannelType.GROUP_DM || x === creator_user_id), + }), + ), + nsfw: false, + }).save(); + } - const channel_dto = await DmChannelDTO.from(channel); + const channel_dto = await DmChannelDTO.from(channel); - if (type === ChannelType.GROUP_DM && channel.recipients) { - for (const recipient of channel.recipients) { - await emitEvent({ - event: "CHANNEL_CREATE", - data: channel_dto.excludedRecipients([recipient.user_id]), - user_id: recipient.user_id, - }); - } - } else { - await emitEvent({ - event: "CHANNEL_CREATE", - data: channel_dto, - user_id: creator_user_id, - }); - } + if (type === ChannelType.GROUP_DM && channel.recipients) { + for (const recipient of channel.recipients) { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id, + }); + } + } else { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto, + user_id: creator_user_id, + }); + } - if (recipients.length === 1) return channel_dto; - else return channel_dto.excludedRecipients([creator_user_id]); - } + if (recipients.length === 1) return channel_dto; + else return channel_dto.excludedRecipients([creator_user_id]); + } - static async removeRecipientFromChannel(channel: Channel, user_id: string) { - await Recipient.delete({ channel_id: channel.id, user_id: user_id }); - channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id); + static async removeRecipientFromChannel(channel: Channel, user_id: string) { + await Recipient.delete({ channel_id: channel.id, user_id: user_id }); + channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id); - if (channel.recipients?.length === 0) { - await Channel.deleteChannel(channel); - await emitEvent({ - event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id, - }); - return; - } + if (channel.recipients?.length === 0) { + await Channel.deleteChannel(channel); + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id, + }); + return; + } - await emitEvent({ - event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id, - }); + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id, + }); - //If the owner leave the server user is the new owner - if (channel.owner_id === user_id) { - channel.owner_id = "1"; // The channel is now owned by the server user - await emitEvent({ - event: "CHANNEL_UPDATE", - data: await DmChannelDTO.from(channel, [user_id]), - channel_id: channel.id, - }); - } + //If the owner leave the server user is the new owner + if (channel.owner_id === user_id) { + channel.owner_id = "1"; // The channel is now owned by the server user + await emitEvent({ + event: "CHANNEL_UPDATE", + data: await DmChannelDTO.from(channel, [user_id]), + channel_id: channel.id, + }); + } - await channel.save(); + await channel.save(); - await emitEvent({ - event: "CHANNEL_RECIPIENT_REMOVE", - data: { - channel_id: channel.id, - user: await User.findOneOrFail({ - where: { id: user_id }, - select: PublicUserProjection, - }), - }, - channel_id: channel.id, - } as ChannelRecipientRemoveEvent); - } + await emitEvent({ + event: "CHANNEL_RECIPIENT_REMOVE", + data: { + channel_id: channel.id, + user: await User.findOneOrFail({ + where: { id: user_id }, + select: PublicUserProjection, + }), + }, + channel_id: channel.id, + } as ChannelRecipientRemoveEvent); + } - static async deleteChannel(channel: Channel) { - // TODO Delete attachments from the CDN for messages in the channel - await Channel.delete({ id: channel.id }); + static async deleteChannel(channel: Channel) { + // TODO Delete attachments from the CDN for messages in the channel + await Channel.delete({ id: channel.id }); - if (channel.guild_id) { - const guild = await Guild.findOneOrFail({ - where: { id: channel.guild_id }, - select: { channel_ordering: true }, - }); + if (channel.guild_id) { + const guild = await Guild.findOneOrFail({ + where: { id: channel.guild_id }, + select: { channel_ordering: true }, + }); - const updatedOrdering = guild.channel_ordering.filter((id) => id != channel.id); - await Guild.update({ id: channel.guild_id }, { channel_ordering: updatedOrdering }); - } - } + const updatedOrdering = guild.channel_ordering.filter((id) => id != channel.id); + await Guild.update({ id: channel.guild_id }, { channel_ordering: updatedOrdering }); + } + } - static async calculatePosition(channel_id: string, guild_id: string, guild?: Guild) { - if (!guild) - guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { channel_ordering: true }, - }); + static async calculatePosition(channel_id: string, guild_id: string, guild?: Guild) { + if (!guild) + guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { channel_ordering: true }, + }); - return guild.channel_ordering.findIndex((id) => channel_id == id); - } + return guild.channel_ordering.findIndex((id) => channel_id == id); + } - static async getOrderedChannels(guild_id: string, guild?: Guild) { - if (!guild) - guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { channel_ordering: true }, - }); + static async getOrderedChannels(guild_id: string, guild?: Guild) { + if (!guild) + guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { channel_ordering: true }, + }); - const channels = await Promise.all(guild.channel_ordering.map((id) => Channel.findOne({ where: { id } }))); + const channels = await Promise.all(guild.channel_ordering.map((id) => Channel.findOne({ where: { id } }))); - return channels - .filter((channel) => channel !== null) - .reduce((r, v) => { - v = v as Channel; + return channels + .filter((channel) => channel !== null) + .reduce((r, v) => { + v = v as Channel; - v.position = (guild as Guild).channel_ordering.indexOf(v.id); - r[v.position] = v; - return r; - }, [] as Array); - } + v.position = (guild as Guild).channel_ordering.indexOf(v.id); + r[v.position] = v; + return r; + }, [] as Array); + } - isDm() { - return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM; - } + isDm() { + return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM; + } - // Does the channel support sending messages ( eg categories do not ) - isWritable() { - const disallowedChannelTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_STAGE_VOICE, ChannelType.VOICELESS_WHITEBOARD]; - return disallowedChannelTypes.indexOf(this.type) == -1; - } + // Does the channel support sending messages ( eg categories do not ) + isWritable() { + const disallowedChannelTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_STAGE_VOICE, ChannelType.VOICELESS_WHITEBOARD]; + return disallowedChannelTypes.indexOf(this.type) == -1; + } - async getUserPermissions(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise { - if (this.isDm()) return this.owner_id == (opts.user_id ?? opts.user?.id) ? Permissions.ALL : Permissions.DEFAULT_DM_PERMISSIONS; - let guild = opts.guild; - if (!guild) { - if (this.guild) guild = this.guild; - else if (this.guild_id) guild = await Guild.findOneOrFail({ where: { id: this.guild_id } }); - else { - console.error("Channel.getUserPermissions: called without guild for non-DM channel."); - return Permissions.NONE; - } - } + async getUserPermissions(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise { + if (this.isDm()) return this.owner_id == (opts.user_id ?? opts.user?.id) ? Permissions.ALL : Permissions.DEFAULT_DM_PERMISSIONS; + let guild = opts.guild; + if (!guild) { + if (this.guild) guild = this.guild; + else if (this.guild_id) guild = await Guild.findOneOrFail({ where: { id: this.guild_id } }); + else { + console.error("Channel.getUserPermissions: called without guild for non-DM channel."); + return Permissions.NONE; + } + } - // check if we can resolve here to short-circuit possibly calling the database unnecessarily - // TODO: do we want to have an instance-wide opt out of this behavior? It would just be an extra if statement here - const ownerId = guild?.owner?.id ?? guild?.owner_id; - if (!!opts.user_id && ownerId === opts.user_id) return Permissions.ALL; - if (!!opts.user?.id && ownerId === opts.user?.id) return Permissions.ALL; - if (!!opts.member?.id && ownerId === opts.member?.id) return Permissions.ALL; + // check if we can resolve here to short-circuit possibly calling the database unnecessarily + // TODO: do we want to have an instance-wide opt out of this behavior? It would just be an extra if statement here + const ownerId = guild?.owner?.id ?? guild?.owner_id; + if (!!opts.user_id && ownerId === opts.user_id) return Permissions.ALL; + if (!!opts.user?.id && ownerId === opts.user?.id) return Permissions.ALL; + if (!!opts.member?.id && ownerId === opts.member?.id) return Permissions.ALL; - let member = opts.member; - if (!member) { - if (opts.user) member = await Member.findOneOrFail({ where: { guild_id: guild.id, id: opts.user.id }, relations: ["roles"] }); - else if (opts.user_id) member = await Member.findOneOrFail({ where: { guild_id: guild.id, id: opts.user_id }, relations: ["roles"] }); - else { - console.error("Channel.getUserPermissions: called without user or member for non-DM channel."); - return Permissions.NONE; - } - } + let member = opts.member; + if (!member) { + if (opts.user) member = await Member.findOneOrFail({ where: { guild_id: guild.id, id: opts.user.id }, relations: ["roles"] }); + else if (opts.user_id) member = await Member.findOneOrFail({ where: { guild_id: guild.id, id: opts.user_id }, relations: ["roles"] }); + else { + console.error("Channel.getUserPermissions: called without user or member for non-DM channel."); + return Permissions.NONE; + } + } - const roles = ( - member.roles || - ( - await Member.findOneOrFail({ - where: { guild_id: guild.id, index: member.index }, - relations: ["roles"], - select: { - roles: { - id: true, - permissions: true, - position: true, - }, - }, - loadEagerRelations: false, - }) - ).roles - ).sort((a, b) => a.position - b.position); // ascending by position + const roles = ( + member.roles || + ( + await Member.findOneOrFail({ + where: { guild_id: guild.id, index: member.index }, + relations: ["roles"], + select: { + roles: { + id: true, + permissions: true, + position: true, + }, + }, + loadEagerRelations: false, + }) + ).roles + ).sort((a, b) => a.position - b.position); // ascending by position - return Permissions.finalPermission({ - user: { - ...member, - roles: roles.map((r) => r.id), - flags: member.user?.flags ?? (await User.findOneOrFail({ where: { id: member.id }, select: { flags: true } })).flags, - }, - guild: { id: guild.id, owner_id: guild.owner_id!, roles }, // We don't care about including *all* guild roles, as not all of them are relevant... - channel: this, - }); - } + return Permissions.finalPermission({ + user: { + ...member, + roles: roles.map((r) => r.id), + flags: member.user?.flags ?? (await User.findOneOrFail({ where: { id: member.id }, select: { flags: true } })).flags, + }, + guild: { id: guild.id, owner_id: guild.owner_id!, roles }, // We don't care about including *all* guild roles, as not all of them are relevant... + channel: this, + }); + } - // TODO: should we throw for missing args? - async canViewChannel(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise { - if (this.isDm()) return await this.canViewDmChannel(opts.user_id, opts.user); + // TODO: should we throw for missing args? + async canViewChannel(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise { + if (this.isDm()) return await this.canViewDmChannel(opts.user_id, opts.user); - const userPerms = await this.getUserPermissions(opts); - return userPerms.has("VIEW_CHANNEL"); - } + const userPerms = await this.getUserPermissions(opts); + return userPerms.has("VIEW_CHANNEL"); + } - private async canViewDmChannel(user_id?: string, user?: User): Promise { - const userId = user_id ?? user?.id; - if (!userId) { - console.error("Channel.canViewChannel: called without user for DM channel."); - return false; - } - if (!user) return false; - if (this.recipients) return this.recipients.some((r) => r.user_id === user.id && !r.closed); - else { - // we dont have recipients on hand - const recipient = await Recipient.findOne({ where: { channel_id: this.id, user_id: user.id } }); - return recipient == null ? false : !recipient.closed; - } - } + private async canViewDmChannel(user_id?: string, user?: User): Promise { + const userId = user_id ?? user?.id; + if (!userId) { + console.error("Channel.canViewChannel: called without user for DM channel."); + return false; + } + if (!user) return false; + if (this.recipients) return this.recipients.some((r) => r.user_id === user.id && !r.closed); + else { + // we dont have recipients on hand + const recipient = await Recipient.findOne({ where: { channel_id: this.id, user_id: user.id } }); + return recipient == null ? false : !recipient.closed; + } + } - toJSON() { - return { - ...this, + toJSON() { + return { + ...this, - // these fields are not returned depending on the type of channel - bitrate: this.bitrate || undefined, - user_limit: this.user_limit || undefined, - rate_limit_per_user: this.rate_limit_per_user || undefined, - owner_id: this.owner_id || undefined, - }; - } + // these fields are not returned depending on the type of channel + bitrate: this.bitrate || undefined, + user_limit: this.user_limit || undefined, + rate_limit_per_user: this.rate_limit_per_user || undefined, + owner_id: this.owner_id || undefined, + }; + } } diff --git a/src/util/entities/ClientRelease.ts b/src/util/entities/ClientRelease.ts index 0da1f0b..fc8fe86 100644 --- a/src/util/entities/ClientRelease.ts +++ b/src/util/entities/ClientRelease.ts @@ -20,24 +20,24 @@ import { BaseClass } from "./BaseClass"; @Entity({ - name: "client_release", + name: "client_release", }) export class ClientRelease extends BaseClass { - @Column() - name: string; + @Column() + name: string; - @Column() - pub_date: Date; + @Column() + pub_date: Date; - @Column() - url: string; + @Column() + url: string; - @Column() - platform: string; + @Column() + platform: string; - @Column() - enabled: boolean; + @Column() + enabled: boolean; - @Column({ nullable: true }) - notes?: string; + @Column({ nullable: true }) + notes?: string; } diff --git a/src/util/entities/CloudAttachment.ts b/src/util/entities/CloudAttachment.ts index de1e34e..408df02 100644 --- a/src/util/entities/CloudAttachment.ts +++ b/src/util/entities/CloudAttachment.ts @@ -22,60 +22,60 @@ import { Channel } from "./Channel"; @Entity({ - name: "cloud_attachments", + name: "cloud_attachments", }) export class CloudAttachment extends BaseClass { - // Internal tracking metadata - @Column({ name: "user_id", nullable: true }) - @RelationId((att: CloudAttachment) => att.user) - userId: string; + // Internal tracking metadata + @Column({ name: "user_id", nullable: true }) + @RelationId((att: CloudAttachment) => att.user) + userId: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { nullable: true, onDelete: "SET NULL" }) - user?: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { nullable: true, onDelete: "SET NULL" }) + user?: User; - @Column({ name: "channel_id", nullable: true }) - @RelationId((att: CloudAttachment) => att.channel) - channelId?: string; // channel the file is uploaded to + @Column({ name: "channel_id", nullable: true }) + @RelationId((att: CloudAttachment) => att.channel) + channelId?: string; // channel the file is uploaded to - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel, { nullable: true, onDelete: "SET NULL" }) - channel?: Channel; // channel the file is uploaded to + @JoinColumn({ name: "channel_id" }) + @ManyToOne(() => Channel, { nullable: true, onDelete: "SET NULL" }) + channel?: Channel; // channel the file is uploaded to - @Column({ name: "upload_filename" }) - uploadFilename: string; + @Column({ name: "upload_filename" }) + uploadFilename: string; - // User-provided info - @Column({ name: "user_attachment_id", nullable: true }) - userAttachmentId?: string; + // User-provided info + @Column({ name: "user_attachment_id", nullable: true }) + userAttachmentId?: string; - @Column({ name: "user_filename" }) - userFilename: string; // name of file attached + @Column({ name: "user_filename" }) + userFilename: string; // name of file attached - @Column({ name: "user_file_size", nullable: true }) - userFileSize?: number; // size of file in bytes + @Column({ name: "user_file_size", nullable: true }) + userFileSize?: number; // size of file in bytes - @Column({ name: "user_original_content_type", nullable: true }) - userOriginalContentType?: string; + @Column({ name: "user_original_content_type", nullable: true }) + userOriginalContentType?: string; - @Column({ name: "user_is_clip", nullable: true }) - userIsClip?: boolean; // whether the file is a clip + @Column({ name: "user_is_clip", nullable: true }) + userIsClip?: boolean; // whether the file is a clip - // Actual file info, initialised after upload - @Column({ nullable: true }) - size?: number; // size of file in bytes + // Actual file info, initialised after upload + @Column({ nullable: true }) + size?: number; // size of file in bytes - @Column({ nullable: true }) - height?: number; // height of file (if image) + @Column({ nullable: true }) + height?: number; // height of file (if image) - @Column({ nullable: true }) - width?: number; // width of file (if image) + @Column({ nullable: true }) + width?: number; // width of file (if image) - @Column({ name: "content_type", nullable: true }) - contentType?: string; + @Column({ name: "content_type", nullable: true }) + contentType?: string; - // @BeforeRemove() - // onDelete() { - // return deleteFile(new URL(this.url).pathname); - // } + // @BeforeRemove() + // onDelete() { + // return deleteFile(new URL(this.url).pathname); + // } } diff --git a/src/util/entities/Config.ts b/src/util/entities/Config.ts index 964cb22..af53eb2 100644 --- a/src/util/entities/Config.ts +++ b/src/util/entities/Config.ts @@ -20,12 +20,12 @@ import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass"; @Entity({ - name: "config", + name: "config", }) export class ConfigEntity extends BaseClassWithoutId { - @PrimaryIdColumn() - key: string; + @PrimaryIdColumn() + key: string; - @Column({ type: "simple-json", nullable: true }) - value: number | boolean | null | string | undefined; + @Column({ type: "simple-json", nullable: true }) + value: number | boolean | null | string | undefined; } diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts index b2037cc..7c5ffb5 100644 --- a/src/util/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts @@ -22,62 +22,62 @@ import { User } from "./User"; @Entity({ - name: "connected_accounts", + name: "connected_accounts", }) export class ConnectedAccount extends BaseClass { - @Column() - external_id: string; + @Column() + external_id: string; - @Column({ nullable: true }) - @RelationId((account: ConnectedAccount) => account.user) - user_id: string; + @Column({ nullable: true }) + @RelationId((account: ConnectedAccount) => account.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - @Column({ select: false }) - friend_sync?: boolean = false; + @Column({ select: false }) + friend_sync?: boolean = false; - @Column() - name: string; + @Column() + name: string; - @Column({ select: false }) - revoked?: boolean = false; + @Column({ select: false }) + revoked?: boolean = false; - @Column({ select: false }) - show_activity?: number = 0; + @Column({ select: false }) + show_activity?: number = 0; - @Column() - type: string; + @Column() + type: string; - @Column() - verified?: boolean = true; + @Column() + verified?: boolean = true; - @Column({ select: false }) - visibility?: number = 0; + @Column({ select: false }) + visibility?: number = 0; - @Column({ type: "simple-array" }) - integrations?: string[] = []; + @Column({ type: "simple-array" }) + integrations?: string[] = []; - @Column({ type: "simple-json", name: "metadata", nullable: true }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - metadata_?: any; + @Column({ type: "simple-json", name: "metadata", nullable: true }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + metadata_?: any; - @Column() - metadata_visibility?: number = 0; + @Column() + metadata_visibility?: number = 0; - @Column() - two_way_link?: boolean = false; + @Column() + two_way_link?: boolean = false; - @Column({ select: false, nullable: true, type: "simple-json" }) - token_data?: ConnectedAccountTokenData | null; + @Column({ select: false, nullable: true, type: "simple-json" }) + token_data?: ConnectedAccountTokenData | null; - async revoke() { - this.revoked = true; - this.token_data = null; - await this.save(); - } + async revoke() { + this.revoked = true; + this.token_data = null; + await this.save(); + } } diff --git a/src/util/entities/ConnectionConfigEntity.ts b/src/util/entities/ConnectionConfigEntity.ts index 75c4a80..b10f576 100644 --- a/src/util/entities/ConnectionConfigEntity.ts +++ b/src/util/entities/ConnectionConfigEntity.ts @@ -20,12 +20,12 @@ import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass"; @Entity({ - name: "connection_config", + name: "connection_config", }) export class ConnectionConfigEntity extends BaseClassWithoutId { - @PrimaryIdColumn() - key: string; + @PrimaryIdColumn() + key: string; - @Column({ type: "simple-json", nullable: true }) - value: number | boolean | null | string | Date | undefined; + @Column({ type: "simple-json", nullable: true }) + value: number | boolean | null | string | Date | undefined; } diff --git a/src/util/entities/EmbedCache.ts b/src/util/entities/EmbedCache.ts index 2b481ad..db1fbba 100644 --- a/src/util/entities/EmbedCache.ts +++ b/src/util/entities/EmbedCache.ts @@ -21,12 +21,12 @@ import { Embed } from "@spacebar/schemas"; @Entity({ - name: "embed_cache", + name: "embed_cache", }) export class EmbedCache extends BaseClass { - @Column() - url: string; + @Column() + url: string; - @Column({ type: "simple-json" }) - embed: Embed; + @Column({ type: "simple-json" }) + embed: Embed; } diff --git a/src/util/entities/Emoji.ts b/src/util/entities/Emoji.ts index 1a71e59..4c22921 100644 --- a/src/util/entities/Emoji.ts +++ b/src/util/entities/Emoji.ts @@ -22,44 +22,44 @@ import { Guild } from "./Guild"; @Entity({ - name: "emojis", + name: "emojis", }) export class Emoji extends BaseClass { - @Column() - animated: boolean; + @Column() + animated: boolean; - @Column() - available: boolean; // whether this emoji can be used, may be false due to various reasons + @Column() + available: boolean; // whether this emoji can be used, may be false due to various reasons - @Column() - guild_id: string; + @Column() + guild_id: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild) => guild.emojis, { - onDelete: "CASCADE", - }) - guild: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, (guild) => guild.emojis, { + onDelete: "CASCADE", + }) + guild: Guild; - @Column({ nullable: true }) - @RelationId((emoji: Emoji) => emoji.user) - user_id: string; + @Column({ nullable: true }) + @RelationId((emoji: Emoji) => emoji.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User) + user: User; - @Column() - managed: boolean; + @Column() + managed: boolean; - @Column() - name: string; + @Column() + name: string; - @Column() - require_colons: boolean; + @Column() + require_colons: boolean; - @Column({ type: "simple-array" }) - roles: string[]; // roles this emoji is whitelisted to (new discord feature?) + @Column({ type: "simple-array" }) + roles: string[]; // roles this emoji is whitelisted to (new discord feature?) - @Column({ type: "simple-array", nullable: true }) - groups: string[]; // user groups this emoji is whitelisted to (Spacebar extension) + @Column({ type: "simple-array", nullable: true }) + groups: string[]; // user groups this emoji is whitelisted to (Spacebar extension) } diff --git a/src/util/entities/Encryption.ts b/src/util/entities/Encryption.ts index 12be1d0..ee92bd3 100644 --- a/src/util/entities/Encryption.ts +++ b/src/util/entities/Encryption.ts @@ -20,24 +20,24 @@ import { BaseClass } from "./BaseClass"; @Entity({ - name: "security_settings", + name: "security_settings", }) export class SecuritySettings extends BaseClass { - @Column({ nullable: true }) - guild_id: string; + @Column({ nullable: true }) + guild_id: string; - @Column({ nullable: true }) - channel_id: string; + @Column({ nullable: true }) + channel_id: string; - @Column() - encryption_permission_mask: number; + @Column() + encryption_permission_mask: number; - @Column({ type: "simple-array" }) - allowed_algorithms: string[]; + @Column({ type: "simple-array" }) + allowed_algorithms: string[]; - @Column() - current_algorithm: string; + @Column() + current_algorithm: string; - @Column({ nullable: true }) - used_since_message: string; + @Column({ nullable: true }) + used_since_message: string; } diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts index 92d4925..56740ab 100644 --- a/src/util/entities/Guild.ts +++ b/src/util/entities/Guild.ts @@ -50,388 +50,388 @@ // ], export const PublicGuildRelations = [ - "channels", - "emojis", - "roles", - "stickers", - "voice_states", - // "members", // TODO: These are public, but all members should not be fetched. - // "members.user", + "channels", + "emojis", + "roles", + "stickers", + "voice_states", + // "members", // TODO: These are public, but all members should not be fetched. + // "members.user", ]; @Entity({ - name: "guilds", + name: "guilds", }) export class Guild extends BaseClass { - @Column({ type: String, nullable: true }) - @RelationId((guild: Guild) => guild.afk_channel) - afk_channel_id?: string | null; + @Column({ type: String, nullable: true }) + @RelationId((guild: Guild) => guild.afk_channel) + afk_channel_id?: string | null; - @JoinColumn({ name: "afk_channel_id" }) - @ManyToOne(() => Channel) - afk_channel?: Channel; + @JoinColumn({ name: "afk_channel_id" }) + @ManyToOne(() => Channel) + afk_channel?: Channel; - @Column({ nullable: true }) - afk_timeout?: number; + @Column({ nullable: true }) + afk_timeout?: number; - // * commented out -> use owner instead - // application id of the guild creator if it is bot-created - // @Column({ nullable: true }) - // application?: string; + // * commented out -> use owner instead + // application id of the guild creator if it is bot-created + // @Column({ nullable: true }) + // application?: string; - @JoinColumn({ name: "ban_ids" }) - @OneToMany(() => Ban, (ban: Ban) => ban.guild, { - cascade: true, - orphanedRowAction: "delete", - }) - bans: Ban[]; + @JoinColumn({ name: "ban_ids" }) + @OneToMany(() => Ban, (ban: Ban) => ban.guild, { + cascade: true, + orphanedRowAction: "delete", + }) + bans: Ban[]; - @Column({ nullable: true }) - banner?: string; + @Column({ nullable: true }) + banner?: string; - @Column({ nullable: true }) - default_message_notifications?: number; + @Column({ nullable: true }) + default_message_notifications?: number; - @Column({ nullable: true }) - description?: string; + @Column({ nullable: true }) + description?: string; - @Column({ nullable: true }) - discovery_splash?: string; + @Column({ nullable: true }) + discovery_splash?: string; - @Column({ nullable: true }) - explicit_content_filter?: number; + @Column({ nullable: true }) + explicit_content_filter?: number; - @Column({ type: "simple-array" }) - features: string[] = []; //TODO use enum - //TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features + @Column({ type: "simple-array" }) + features: string[] = []; //TODO use enum + //TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features - @Column({ nullable: true }) - primary_category_id?: string; // TODO: this was number? + @Column({ nullable: true }) + primary_category_id?: string; // TODO: this was number? - @Column({ nullable: true }) - icon?: string; + @Column({ nullable: true }) + icon?: string; - @Column() - large?: boolean = false; + @Column() + large?: boolean = false; - @Column({ nullable: true }) - max_members?: number; + @Column({ nullable: true }) + max_members?: number; - @Column({ nullable: true }) - max_presences?: number; + @Column({ nullable: true }) + max_presences?: number; - @Column({ nullable: true }) - max_video_channel_users?: number; + @Column({ nullable: true }) + max_video_channel_users?: number; - @Column({ nullable: true }) - member_count?: number; + @Column({ nullable: true }) + member_count?: number; - @Column({ nullable: true }) - presence_count?: number; // users online + @Column({ nullable: true }) + presence_count?: number; // users online - @OneToMany(() => Member, (member: Member) => member.guild, { - cascade: true, - orphanedRowAction: "delete", - onDelete: "CASCADE", - }) - members: Member[]; + @OneToMany(() => Member, (member: Member) => member.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + members: Member[]; - @JoinColumn({ name: "role_ids" }) - @OneToMany(() => Role, (role: Role) => role.guild, { - cascade: true, - orphanedRowAction: "delete", - onDelete: "CASCADE", - }) - roles: Role[]; + @JoinColumn({ name: "role_ids" }) + @OneToMany(() => Role, (role: Role) => role.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + roles: Role[]; - @JoinColumn({ name: "channel_ids" }) - @OneToMany(() => Channel, (channel: Channel) => channel.guild, { - cascade: true, - orphanedRowAction: "delete", - }) - channels: Channel[]; + @JoinColumn({ name: "channel_ids" }) + @OneToMany(() => Channel, (channel: Channel) => channel.guild, { + cascade: true, + orphanedRowAction: "delete", + }) + channels: Channel[]; - @Column({ nullable: true }) - @RelationId((guild: Guild) => guild.template) - template_id?: string; + @Column({ nullable: true }) + @RelationId((guild: Guild) => guild.template) + template_id?: string; - @JoinColumn({ name: "template_id", referencedColumnName: "id" }) - @ManyToOne(() => Template) - template: Template; + @JoinColumn({ name: "template_id", referencedColumnName: "id" }) + @ManyToOne(() => Template) + template: Template; - @JoinColumn({ name: "emoji_ids" }) - @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild, { - cascade: true, - orphanedRowAction: "delete", - onDelete: "CASCADE", - }) - emojis: Emoji[]; + @JoinColumn({ name: "emoji_ids" }) + @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + emojis: Emoji[]; - @JoinColumn({ name: "sticker_ids" }) - @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild, { - cascade: true, - orphanedRowAction: "delete", - onDelete: "CASCADE", - }) - stickers: Sticker[]; + @JoinColumn({ name: "sticker_ids" }) + @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + stickers: Sticker[]; - @JoinColumn({ name: "invite_ids" }) - @OneToMany(() => Invite, (invite: Invite) => invite.guild, { - cascade: true, - orphanedRowAction: "delete", - onDelete: "CASCADE", - }) - invites: Invite[]; + @JoinColumn({ name: "invite_ids" }) + @OneToMany(() => Invite, (invite: Invite) => invite.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + invites: Invite[]; - @JoinColumn({ name: "voice_state_ids" }) - @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild, { - cascade: true, - orphanedRowAction: "delete", - onDelete: "CASCADE", - }) - voice_states: VoiceState[]; + @JoinColumn({ name: "voice_state_ids" }) + @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + voice_states: VoiceState[]; - @JoinColumn({ name: "webhook_ids" }) - @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild, { - cascade: true, - orphanedRowAction: "delete", - onDelete: "CASCADE", - }) - webhooks: Webhook[]; + @JoinColumn({ name: "webhook_ids" }) + @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild, { + cascade: true, + orphanedRowAction: "delete", + onDelete: "CASCADE", + }) + webhooks: Webhook[]; - @Column({ nullable: true }) - mfa_level?: number; + @Column({ nullable: true }) + mfa_level?: number; - @Column() - name: string; + @Column() + name: string; - @Column({ nullable: true }) - @RelationId((guild: Guild) => guild.owner) - owner_id?: string; // optional to allow for ownerless guilds + @Column({ nullable: true }) + @RelationId((guild: Guild) => guild.owner) + owner_id?: string; // optional to allow for ownerless guilds - @JoinColumn({ name: "owner_id", referencedColumnName: "id" }) - @ManyToOne(() => User) - owner?: User; // optional to allow for ownerless guilds + @JoinColumn({ name: "owner_id", referencedColumnName: "id" }) + @ManyToOne(() => User) + owner?: User; // optional to allow for ownerless guilds - @Column({ nullable: true }) - preferred_locale?: string; + @Column({ nullable: true }) + preferred_locale?: string; - @Column({ nullable: true }) - premium_subscription_count?: number; + @Column({ nullable: true }) + premium_subscription_count?: number; - @Column() - premium_tier?: number; // crowd premium level + @Column() + premium_tier?: number; // crowd premium level - @Column({ type: String, nullable: true }) - @RelationId((guild: Guild) => guild.public_updates_channel) - public_updates_channel_id: string | null; + @Column({ type: String, nullable: true }) + @RelationId((guild: Guild) => guild.public_updates_channel) + public_updates_channel_id: string | null; - @JoinColumn({ name: "public_updates_channel_id" }) - @ManyToOne(() => Channel) - public_updates_channel?: Channel; + @JoinColumn({ name: "public_updates_channel_id" }) + @ManyToOne(() => Channel) + public_updates_channel?: Channel; - @Column({ type: String, nullable: true }) - @RelationId((guild: Guild) => guild.rules_channel) - rules_channel_id?: string | null; + @Column({ type: String, nullable: true }) + @RelationId((guild: Guild) => guild.rules_channel) + rules_channel_id?: string | null; - @JoinColumn({ name: "rules_channel_id" }) - @ManyToOne(() => Channel) - rules_channel?: string; + @JoinColumn({ name: "rules_channel_id" }) + @ManyToOne(() => Channel) + rules_channel?: string; - @Column({ nullable: true }) - region?: string; + @Column({ nullable: true }) + region?: string; - @Column({ nullable: true }) - splash?: string; + @Column({ nullable: true }) + splash?: string; - @Column({ type: String, nullable: true }) - @RelationId((guild: Guild) => guild.system_channel) - system_channel_id?: string | null; + @Column({ type: String, nullable: true }) + @RelationId((guild: Guild) => guild.system_channel) + system_channel_id?: string | null; - @JoinColumn({ name: "system_channel_id" }) - @ManyToOne(() => Channel) - system_channel?: Channel; + @JoinColumn({ name: "system_channel_id" }) + @ManyToOne(() => Channel) + system_channel?: Channel; - @Column({ nullable: true }) - system_channel_flags?: number; + @Column({ nullable: true }) + system_channel_flags?: number; - @Column() - unavailable: boolean = false; + @Column() + unavailable: boolean = false; - @Column({ nullable: true }) - verification_level?: number; + @Column({ nullable: true }) + verification_level?: number; - /** - * DEPRECATED: Look at the new Guild onboarding screens. - */ - @Column({ type: "simple-json" }) - welcome_screen: GuildWelcomeScreen; + /** + * DEPRECATED: Look at the new Guild onboarding screens. + */ + @Column({ type: "simple-json" }) + welcome_screen: GuildWelcomeScreen; - @Column({ nullable: true }) - @RelationId((guild: Guild) => guild.widget_channel) - widget_channel_id?: string; + @Column({ nullable: true }) + @RelationId((guild: Guild) => guild.widget_channel) + widget_channel_id?: string; - @JoinColumn({ name: "widget_channel_id" }) - @ManyToOne(() => Channel) - widget_channel?: Channel; + @JoinColumn({ name: "widget_channel_id" }) + @ManyToOne(() => Channel) + widget_channel?: Channel; - @Column() - widget_enabled: boolean = true; + @Column() + widget_enabled: boolean = true; - @Column({ nullable: true }) - nsfw_level?: number; + @Column({ nullable: true }) + nsfw_level?: number; - @Column() - nsfw: boolean = false; + @Column() + nsfw: boolean = false; - // TODO: nested guilds - @Column({ nullable: true }) - parent?: string; + // TODO: nested guilds + @Column({ nullable: true }) + parent?: string; - // only for developer portal - permissions?: number; + // only for developer portal + permissions?: number; - //new guild settings, 11/08/2022: - @Column({ nullable: true }) - premium_progress_bar_enabled: boolean = false; + //new guild settings, 11/08/2022: + @Column({ nullable: true }) + premium_progress_bar_enabled: boolean = false; - @Column({ select: false, type: "simple-array" }) - channel_ordering: string[]; + @Column({ select: false, type: "simple-array" }) + channel_ordering: string[]; - static async createGuild(body: { - name?: string; - icon?: string | null; - owner_id?: string; - roles?: Partial[]; - channels?: Partial[]; - template_guild_id: string | null; - }) { - const guild_id = Snowflake.generate(); + static async createGuild(body: { + name?: string; + icon?: string | null; + owner_id?: string; + roles?: Partial[]; + channels?: Partial[]; + template_guild_id: string | null; + }) { + const guild_id = Snowflake.generate(); - const guild = await Guild.create({ - id: guild_id, - name: body.name || "Spacebar", - icon: await handleFile(`/icons/${guild_id}`, body.icon as string), - owner_id: body.owner_id, // TODO: need to figure out a way for ownerless guilds and multiply-owned guilds - presence_count: 0, - member_count: 0, // will automatically be increased by addMember() - mfa_level: 0, - preferred_locale: "en-US", - premium_subscription_count: 0, - premium_tier: 0, - system_channel_flags: 4, // defaults effect: suppress the setup tips to save performance - nsfw_level: 0, - verification_level: 0, - welcome_screen: { - enabled: false, - description: "", - welcome_channels: [], - }, - channel_ordering: [], - afk_timeout: Config.get().defaults.guild.afkTimeout, - default_message_notifications: Config.get().defaults.guild.defaultMessageNotifications, - explicit_content_filter: Config.get().defaults.guild.explicitContentFilter, - features: Config.get().guild.defaultFeatures, - max_members: Config.get().limits.guild.maxMembers, - max_presences: Config.get().defaults.guild.maxPresences, - max_video_channel_users: Config.get().defaults.guild.maxVideoChannelUsers, - region: Config.get().regions.default, - }).save(); + const guild = await Guild.create({ + id: guild_id, + name: body.name || "Spacebar", + icon: await handleFile(`/icons/${guild_id}`, body.icon as string), + owner_id: body.owner_id, // TODO: need to figure out a way for ownerless guilds and multiply-owned guilds + presence_count: 0, + member_count: 0, // will automatically be increased by addMember() + mfa_level: 0, + preferred_locale: "en-US", + premium_subscription_count: 0, + premium_tier: 0, + system_channel_flags: 4, // defaults effect: suppress the setup tips to save performance + nsfw_level: 0, + verification_level: 0, + welcome_screen: { + enabled: false, + description: "", + welcome_channels: [], + }, + channel_ordering: [], + afk_timeout: Config.get().defaults.guild.afkTimeout, + default_message_notifications: Config.get().defaults.guild.defaultMessageNotifications, + explicit_content_filter: Config.get().defaults.guild.explicitContentFilter, + features: Config.get().guild.defaultFeatures, + max_members: Config.get().limits.guild.maxMembers, + max_presences: Config.get().defaults.guild.maxPresences, + max_video_channel_users: Config.get().defaults.guild.maxVideoChannelUsers, + region: Config.get().regions.default, + }).save(); - // we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error - // TODO: make the @everyone a pseudorole that is dynamically generated at runtime so we can save storage - await Role.create({ - id: guild_id, - guild_id: guild_id, - color: 0, - colors: { primary_color: 0 }, - hoist: false, - managed: false, - mentionable: false, - name: "@everyone", - permissions: "2251804225", - position: 0, - icon: undefined, - unicode_emoji: undefined, - flags: 0, // TODO? - }).save(); + // we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error + // TODO: make the @everyone a pseudorole that is dynamically generated at runtime so we can save storage + await Role.create({ + id: guild_id, + guild_id: guild_id, + color: 0, + colors: { primary_color: 0 }, + hoist: false, + managed: false, + mentionable: false, + name: "@everyone", + permissions: "2251804225", + position: 0, + icon: undefined, + unicode_emoji: undefined, + flags: 0, // TODO? + }).save(); - // create custom roles if provided - if (body.roles && body.roles.length) { - await Promise.all( - body.roles?.map((role) => { - new Promise((resolve) => { - Role.create({ - ...role, - guild_id, - id: - // role.id === body.template_guild_id indicates that this is the @everyone role - role.id === body.template_guild_id ? guild_id : Snowflake.generate(), - }) - .save() - .then(resolve); - }); - }), - ); - } + // create custom roles if provided + if (body.roles && body.roles.length) { + await Promise.all( + body.roles?.map((role) => { + new Promise((resolve) => { + Role.create({ + ...role, + guild_id, + id: + // role.id === body.template_guild_id indicates that this is the @everyone role + role.id === body.template_guild_id ? guild_id : Snowflake.generate(), + }) + .save() + .then(resolve); + }); + }), + ); + } - if (!body.channels || !body.channels.length) { - body.channels = [{ id: "01", type: 0, name: "general", nsfw: false }]; - } + if (!body.channels || !body.channels.length) { + body.channels = [{ id: "01", type: 0, name: "general", nsfw: false }]; + } - const ids = new Map(); + const ids = new Map(); - body.channels.forEach((x) => { - if (x.id) { - ids.set(x.id, Snowflake.generate()); - } - }); + body.channels.forEach((x) => { + if (x.id) { + ids.set(x.id, Snowflake.generate()); + } + }); - for (const channel of body.channels.sort((a) => (a.parent_id ? 1 : -1))) { - const id = ids.get(channel.id) || Snowflake.generate(); + for (const channel of body.channels.sort((a) => (a.parent_id ? 1 : -1))) { + const id = ids.get(channel.id) || Snowflake.generate(); - const parent_id = ids.get(channel.parent_id); + const parent_id = ids.get(channel.parent_id); - const saved = await Channel.createChannel({ ...channel, guild_id, id, parent_id }, body.owner_id, { - keepId: true, - skipExistsCheck: true, - skipPermissionCheck: true, - skipEventEmit: true, - }); + const saved = await Channel.createChannel({ ...channel, guild_id, id, parent_id }, body.owner_id, { + keepId: true, + skipExistsCheck: true, + skipPermissionCheck: true, + skipEventEmit: true, + }); - await Guild.insertChannelInOrder(guild.id, saved.id, parent_id ?? channel.position ?? 0, guild); - } + await Guild.insertChannelInOrder(guild.id, saved.id, parent_id ?? channel.position ?? 0, guild); + } - return guild; - } + return guild; + } - /** Insert a channel into the guild ordering by parent channel id or position */ - static async insertChannelInOrder(guild_id: string, channel_id: string, position: number, guild?: Guild): Promise; - static async insertChannelInOrder(guild_id: string, channel_id: string, parent_id: string, guild?: Guild): Promise; - static async insertChannelInOrder(guild_id: string, channel_id: string, insertPoint: string | number, guild?: Guild): Promise; - static async insertChannelInOrder(guild_id: string, channel_id: string, insertPoint: string | number, guild?: Guild): Promise { - if (!guild) - guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { channel_ordering: true }, - }); + /** Insert a channel into the guild ordering by parent channel id or position */ + static async insertChannelInOrder(guild_id: string, channel_id: string, position: number, guild?: Guild): Promise; + static async insertChannelInOrder(guild_id: string, channel_id: string, parent_id: string, guild?: Guild): Promise; + static async insertChannelInOrder(guild_id: string, channel_id: string, insertPoint: string | number, guild?: Guild): Promise; + static async insertChannelInOrder(guild_id: string, channel_id: string, insertPoint: string | number, guild?: Guild): Promise { + if (!guild) + guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { channel_ordering: true }, + }); - let position; - if (typeof insertPoint == "string") position = guild.channel_ordering.indexOf(insertPoint) + 1; - else position = insertPoint; + let position; + if (typeof insertPoint == "string") position = guild.channel_ordering.indexOf(insertPoint) + 1; + else position = insertPoint; - arrayRemove(guild.channel_ordering, channel_id); + arrayRemove(guild.channel_ordering, channel_id); - guild.channel_ordering.splice(position, 0, channel_id); - await Guild.update({ id: guild_id }, { channel_ordering: guild.channel_ordering }); - return position; - } + guild.channel_ordering.splice(position, 0, channel_id); + await Guild.update({ id: guild_id }, { channel_ordering: guild.channel_ordering }); + return position; + } - toJSON(): Guild { - return { - ...this, - unavailable: this.unavailable == false ? undefined : true, - channel_ordering: undefined, - }; - } + toJSON(): Guild { + return { + ...this, + unavailable: this.unavailable == false ? undefined : true, + channel_ordering: undefined, + }; + } } diff --git a/src/util/entities/InstanceBan.ts b/src/util/entities/InstanceBan.ts index 08100a9..2c9d301 100644 --- a/src/util/entities/InstanceBan.ts +++ b/src/util/entities/InstanceBan.ts @@ -23,89 +23,89 @@ import { Guild } from "./Guild"; @Entity({ - name: "instance_bans", + name: "instance_bans", }) export class InstanceBan extends BaseClass { - @Column({ type: "bigint" }) - @CreateDateColumn() - created_at: Date = new Date(); + @Column({ type: "bigint" }) + @CreateDateColumn() + created_at: Date = new Date(); - @Column() - reason: string; + @Column() + reason: string; - @Column({ nullable: true }) - user_id?: string; + @Column({ nullable: true }) + user_id?: string; - @Column({ nullable: true }) - fingerprint?: string; + @Column({ nullable: true }) + fingerprint?: string; - @Column({ nullable: true }) - ip_address?: string; + @Column({ nullable: true }) + ip_address?: string; - // chain of trust type tracking + // chain of trust type tracking - @Column({ default: false }) - is_allowlisted: boolean = false; + @Column({ default: false }) + is_allowlisted: boolean = false; - @Column({ default: false }) - is_from_other_instance_ban: boolean = false; + @Column({ default: false }) + is_from_other_instance_ban: boolean = false; - @Column({ nullable: true }) - @RelationId((instance_ban: InstanceBan) => instance_ban.origin_instance_ban) - origin_instance_ban_id?: string; + @Column({ nullable: true }) + @RelationId((instance_ban: InstanceBan) => instance_ban.origin_instance_ban) + origin_instance_ban_id?: string; - @JoinColumn({ name: "origin_instance_ban_id" }) - @OneToOne(() => InstanceBan, { nullable: true, onDelete: "SET NULL" }) - origin_instance_ban?: InstanceBan; + @JoinColumn({ name: "origin_instance_ban_id" }) + @OneToOne(() => InstanceBan, { nullable: true, onDelete: "SET NULL" }) + origin_instance_ban?: InstanceBan; - static async findInstanceBans(opts: { userId?: string; ipAddress?: string; fingerprint?: string; propagateBan?: boolean }) { - const optionalChecks: FindOptionsWhere[] = [{ user_id: opts.userId }]; - if (opts?.ipAddress) optionalChecks.push({ ip_address: opts.ipAddress }); - if (opts?.fingerprint) optionalChecks.push({ fingerprint: opts.fingerprint }); - const instanceBans = await InstanceBan.find({ where: optionalChecks }); + static async findInstanceBans(opts: { userId?: string; ipAddress?: string; fingerprint?: string; propagateBan?: boolean }) { + const optionalChecks: FindOptionsWhere[] = [{ user_id: opts.userId }]; + if (opts?.ipAddress) optionalChecks.push({ ip_address: opts.ipAddress }); + if (opts?.fingerprint) optionalChecks.push({ fingerprint: opts.fingerprint }); + const instanceBans = await InstanceBan.find({ where: optionalChecks }); - const banReasons = []; - for (const ban of instanceBans) { - if (ban.is_allowlisted) continue; - if (opts?.fingerprint && ban.fingerprint === opts.fingerprint) banReasons.push("fingerprint"); - if (opts?.ipAddress && ban.ip_address === opts.ipAddress) banReasons.push("ipAddress"); - if (opts?.userId && ban.user_id === opts?.userId) banReasons.push("userId"); - } + const banReasons = []; + for (const ban of instanceBans) { + if (ban.is_allowlisted) continue; + if (opts?.fingerprint && ban.fingerprint === opts.fingerprint) banReasons.push("fingerprint"); + if (opts?.ipAddress && ban.ip_address === opts.ipAddress) banReasons.push("ipAddress"); + if (opts?.userId && ban.user_id === opts?.userId) banReasons.push("userId"); + } - const banViralityPromises: Promise[] = []; - if (opts.propagateBan && banReasons.length > 0) { - if (opts?.ipAddress && !instanceBans.find((b) => b.ip_address === opts.ipAddress)) - banViralityPromises.push( - InstanceBan.create({ - user_id: opts.userId, - ip_address: opts.ipAddress, - reason: "Propagated from other instance ban", - is_from_other_instance_ban: true, - origin_instance_ban: instanceBans[0], - }).save(), - ); - if (opts?.fingerprint && !instanceBans.find((b) => b.fingerprint === opts.fingerprint)) - banViralityPromises.push( - InstanceBan.create({ - user_id: opts.userId, - fingerprint: opts.fingerprint, - reason: "Propagated from other instance ban", - is_from_other_instance_ban: true, - origin_instance_ban: instanceBans[0], - }).save(), - ); - if (opts?.userId && !instanceBans.find((b) => b.user_id === opts.userId)) - banViralityPromises.push( - InstanceBan.create({ - user_id: opts.userId, - reason: "Propagated from other instance ban", - is_from_other_instance_ban: true, - origin_instance_ban: instanceBans[0], - }).save(), - ); - } + const banViralityPromises: Promise[] = []; + if (opts.propagateBan && banReasons.length > 0) { + if (opts?.ipAddress && !instanceBans.find((b) => b.ip_address === opts.ipAddress)) + banViralityPromises.push( + InstanceBan.create({ + user_id: opts.userId, + ip_address: opts.ipAddress, + reason: "Propagated from other instance ban", + is_from_other_instance_ban: true, + origin_instance_ban: instanceBans[0], + }).save(), + ); + if (opts?.fingerprint && !instanceBans.find((b) => b.fingerprint === opts.fingerprint)) + banViralityPromises.push( + InstanceBan.create({ + user_id: opts.userId, + fingerprint: opts.fingerprint, + reason: "Propagated from other instance ban", + is_from_other_instance_ban: true, + origin_instance_ban: instanceBans[0], + }).save(), + ); + if (opts?.userId && !instanceBans.find((b) => b.user_id === opts.userId)) + banViralityPromises.push( + InstanceBan.create({ + user_id: opts.userId, + reason: "Propagated from other instance ban", + is_from_other_instance_ban: true, + origin_instance_ban: instanceBans[0], + }).save(), + ); + } - await Promise.all(banViralityPromises); - return banReasons; - } + await Promise.all(banViralityPromises); + return banReasons; + } } diff --git a/src/util/entities/Invite.ts b/src/util/entities/Invite.ts index 1304137..b706681 100644 --- a/src/util/entities/Invite.ts +++ b/src/util/entities/Invite.ts @@ -26,95 +26,95 @@ export const PublicInviteRelation = ["inviter", "guild", "channel"]; @Entity({ - name: "invites", + name: "invites", }) export class Invite extends BaseClassWithoutId { - @PrimaryIdColumn() - code: string; + @PrimaryIdColumn() + code: string; - @Column() - temporary: boolean; + @Column() + temporary: boolean; - @Column() - uses: number; + @Column() + uses: number; - @Column() - max_uses: number; + @Column() + max_uses: number; - @Column() - max_age: number; + @Column() + max_age: number; - @Column() - created_at: Date; + @Column() + created_at: Date; - @Column({ nullable: true }) - expires_at?: Date; + @Column({ nullable: true }) + expires_at?: Date; - @Column({ nullable: true }) - @RelationId((invite: Invite) => invite.guild) - guild_id: string; + @Column({ nullable: true }) + @RelationId((invite: Invite) => invite.guild) + guild_id: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild) => guild.invites, { - onDelete: "CASCADE", - }) - guild: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, (guild) => guild.invites, { + onDelete: "CASCADE", + }) + guild: Guild; - @Column({ nullable: true }) - @RelationId((invite: Invite) => invite.channel) - channel_id: string; + @Column({ nullable: true }) + @RelationId((invite: Invite) => invite.channel) + channel_id: string; - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - channel: Channel; + @JoinColumn({ name: "channel_id" }) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) + channel: Channel; - @Column({ nullable: true }) - @RelationId((invite: Invite) => invite.inviter) - inviter_id?: string; + @Column({ nullable: true }) + @RelationId((invite: Invite) => invite.inviter) + inviter_id?: string; - @JoinColumn({ name: "inviter_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - inviter: User; + @JoinColumn({ name: "inviter_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + inviter: User; - @Column({ nullable: true }) - @RelationId((invite: Invite) => invite.target_user) - target_user_id: string; + @Column({ nullable: true }) + @RelationId((invite: Invite) => invite.target_user) + target_user_id: string; - @JoinColumn({ name: "target_user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - target_user?: string; // could be used for "User specific invites" https://github.com/spacebarchat/server/issues/326 + @JoinColumn({ name: "target_user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + target_user?: string; // could be used for "User specific invites" https://github.com/spacebarchat/server/issues/326 - @Column({ nullable: true }) - target_user_type?: number; + @Column({ nullable: true }) + target_user_type?: number; - @Column({ nullable: true }) - vanity_url?: boolean; + @Column({ nullable: true }) + vanity_url?: boolean; - @Column() - flags: number; + @Column() + flags: number; - isExpired() { - if (this.max_age !== 0 && this.expires_at && this.expires_at < new Date()) return true; - if (this.max_uses !== 0 && this.uses >= this.max_uses) return true; - return false; - } + isExpired() { + if (this.max_age !== 0 && this.expires_at && this.expires_at < new Date()) return true; + if (this.max_uses !== 0 && this.uses >= this.max_uses) return true; + return false; + } - static async joinGuild(user_id: string, code: string) { - const invite = await Invite.findOneOrFail({ where: { code } }); - if (invite.isExpired()) { - await Invite.delete({ code }); - throw new Error("Invite is expired"); - } - if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); - else await invite.save(); + static async joinGuild(user_id: string, code: string) { + const invite = await Invite.findOneOrFail({ where: { code } }); + if (invite.isExpired()) { + await Invite.delete({ code }); + throw new Error("Invite is expired"); + } + if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); + else await invite.save(); - await Member.addToGuild(user_id, invite.guild_id); - return invite; - } + await Member.addToGuild(user_id, invite.guild_id); + return invite; + } } diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts index 99aa19e..4faebda 100644 --- a/src/util/entities/Member.ts +++ b/src/util/entities/Member.ts @@ -31,424 +31,424 @@ import { PublicMember, PublicMemberProjection, UserGuildSettings } from "@spacebar/schemas"; export const MemberPrivateProjection: (keyof Member)[] = [ - "id", - "guild", - "guild_id", - "deaf", - "joined_at", - "last_message_id", - "mute", - "nick", - "pending", - "premium_since", - "roles", - "settings", - "user", - "avatar", - "banner", - "bio", - "theme_colors", - "pronouns", - "communication_disabled_until", + "id", + "guild", + "guild_id", + "deaf", + "joined_at", + "last_message_id", + "mute", + "nick", + "pending", + "premium_since", + "roles", + "settings", + "user", + "avatar", + "banner", + "bio", + "theme_colors", + "pronouns", + "communication_disabled_until", ]; @Entity({ - name: "members", + name: "members", }) @Index(["id", "guild_id"], { unique: true }) export class Member extends BaseClassWithoutId { - @PrimaryGeneratedColumn() - index: string; + @PrimaryGeneratedColumn() + index: string; - @Column() - @RelationId((member: Member) => member.user) - id: string; + @Column() + @RelationId((member: Member) => member.user) + id: string; - @JoinColumn({ name: "id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - @Column() - @RelationId((member: Member) => member.guild) - guild_id: string; + @Column() + @RelationId((member: Member) => member.guild) + guild_id: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { - onDelete: "CASCADE", - }) - guild: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) + guild: Guild; - @Column({ nullable: true }) - nick?: string; + @Column({ nullable: true }) + nick?: string; - @JoinTable({ - name: "member_roles", - joinColumn: { name: "index", referencedColumnName: "index" }, - inverseJoinColumn: { - name: "role_id", - referencedColumnName: "id", - }, - }) - @ManyToMany(() => Role, { cascade: true }) - roles: Role[]; + @JoinTable({ + name: "member_roles", + joinColumn: { name: "index", referencedColumnName: "index" }, + inverseJoinColumn: { + name: "role_id", + referencedColumnName: "id", + }, + }) + @ManyToMany(() => Role, { cascade: true }) + roles: Role[]; - @Column() - joined_at: Date; + @Column() + joined_at: Date; - @Column({ type: "bigint", nullable: true }) - premium_since?: number; + @Column({ type: "bigint", nullable: true }) + premium_since?: number; - @Column() - deaf: boolean; + @Column() + deaf: boolean; - @Column() - mute: boolean; + @Column() + mute: boolean; - @Column() - pending: boolean; + @Column() + pending: boolean; - @Column({ type: "simple-json", select: false }) - settings: UserGuildSettings; + @Column({ type: "simple-json", select: false }) + settings: UserGuildSettings; - @Column({ nullable: true }) - last_message_id?: string; + @Column({ nullable: true }) + last_message_id?: string; - /** + /** @JoinColumn({ name: "id" }) @ManyToOne(() => User, { onDelete: "DO NOTHING", // do not auto-kick force-joined members just because their joiners left the server }) **/ - @Column({ nullable: true }) - joined_by: string; + @Column({ nullable: true }) + joined_by: string; - @Column({ nullable: true }) - avatar?: string; + @Column({ nullable: true }) + avatar?: string; - @Column({ nullable: true }) - banner: string; + @Column({ nullable: true }) + banner: string; - @Column() - bio: string; + @Column() + bio: string; - @Column({ nullable: true, type: "simple-array" }) - theme_colors?: number[]; // TODO: Separate `User` and `UserProfile` models + @Column({ nullable: true, type: "simple-array" }) + theme_colors?: number[]; // TODO: Separate `User` and `UserProfile` models - @Column({ nullable: true }) - pronouns?: string; + @Column({ nullable: true }) + pronouns?: string; - @Column({ nullable: true, type: Date }) - communication_disabled_until: Date | null; + @Column({ nullable: true, type: Date }) + communication_disabled_until: Date | null; - // TODO: add this when we have proper read receipts - // @Column({ type: "simple-json" }) - // read_state: ReadState; + // TODO: add this when we have proper read receipts + // @Column({ type: "simple-json" }) + // read_state: ReadState; - @BeforeUpdate() - @BeforeInsert() - validate() { - if (this.nick) { - this.nick = this.nick.split("\n").join(""); - this.nick = this.nick.split("\t").join(""); - } - if (this.nick === "") this.nick = undefined; - if (this.pronouns === "") this.pronouns = undefined; - } + @BeforeUpdate() + @BeforeInsert() + validate() { + if (this.nick) { + this.nick = this.nick.split("\n").join(""); + this.nick = this.nick.split("\t").join(""); + } + if (this.nick === "") this.nick = undefined; + if (this.pronouns === "") this.pronouns = undefined; + } - static async IsInGuildOrFail(user_id: string, guild_id: string) { - if ( - await Member.count({ - where: { id: user_id, guild: { id: guild_id } }, - }) - ) - return true; - throw new HTTPError("You are not member of this guild", 403); - } + static async IsInGuildOrFail(user_id: string, guild_id: string) { + if ( + await Member.count({ + where: { id: user_id, guild: { id: guild_id } }, + }) + ) + return true; + throw new HTTPError("You are not member of this guild", 403); + } - static async removeFromGuild(user_id: string, guild_id: string) { - const guild = await Guild.findOneOrFail({ - select: ["owner_id"], - where: { id: guild_id }, - }); - if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild"); - const member = await Member.findOneOrFail({ - where: { id: user_id, guild_id }, - relations: ["user"], - }); + static async removeFromGuild(user_id: string, guild_id: string) { + const guild = await Guild.findOneOrFail({ + select: ["owner_id"], + where: { id: guild_id }, + }); + if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild"); + const member = await Member.findOneOrFail({ + where: { id: user_id, guild_id }, + relations: ["user"], + }); - // use promise all to execute all promises at the same time -> save time - return Promise.all([ - Member.delete({ - id: user_id, - guild_id, - }), - Guild.decrement({ id: guild_id }, "member_count", 1), + // use promise all to execute all promises at the same time -> save time + return Promise.all([ + Member.delete({ + id: user_id, + guild_id, + }), + Guild.decrement({ id: guild_id }, "member_count", 1), - emitEvent({ - event: "GUILD_DELETE", - data: { - id: guild_id, - }, - user_id: user_id, - } as GuildDeleteEvent), - emitEvent({ - event: "GUILD_MEMBER_REMOVE", - data: { guild_id, user: member.user.toPublicUser() }, - guild_id, - } as GuildMemberRemoveEvent), - ]); - } + emitEvent({ + event: "GUILD_DELETE", + data: { + id: guild_id, + }, + user_id: user_id, + } as GuildDeleteEvent), + emitEvent({ + event: "GUILD_MEMBER_REMOVE", + data: { guild_id, user: member.user.toPublicUser() }, + guild_id, + } as GuildMemberRemoveEvent), + ]); + } - static async addRole(user_id: string, guild_id: string, role_id: string) { - const [member] = await Promise.all([ - Member.findOneOrFail({ - where: { id: user_id, guild_id }, - relations: ["user", "roles"], // we don't want to load the role objects just the ids - select: { - index: true, - roles: { - id: true, - }, - }, - }), - Role.findOneOrFail({ - where: { id: role_id, guild_id }, - select: ["id"], - }), - ]); - member.roles.push(Role.create({ id: role_id })); + static async addRole(user_id: string, guild_id: string, role_id: string) { + const [member] = await Promise.all([ + Member.findOneOrFail({ + where: { id: user_id, guild_id }, + relations: ["user", "roles"], // we don't want to load the role objects just the ids + select: { + index: true, + roles: { + id: true, + }, + }, + }), + Role.findOneOrFail({ + where: { id: role_id, guild_id }, + select: ["id"], + }), + ]); + member.roles.push(Role.create({ id: role_id })); - await Promise.all([ - member.save(), - emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id, - user: member.user, - roles: member.roles.map((x) => x.id), - }, - guild_id, - } as GuildMemberUpdateEvent), - ]); - } + await Promise.all([ + member.save(), + emitEvent({ + event: "GUILD_MEMBER_UPDATE", + data: { + guild_id, + user: member.user, + roles: member.roles.map((x) => x.id), + }, + guild_id, + } as GuildMemberUpdateEvent), + ]); + } - static async removeRole(user_id: string, guild_id: string, role_id: string) { - const [member] = await Promise.all([ - Member.findOneOrFail({ - where: { id: user_id, guild_id }, - relations: ["user", "roles"], // we don't want to load the role objects just the ids - select: { - index: true, - roles: { - id: true, - }, - }, - }), - Role.findOneOrFail({ where: { id: role_id, guild_id } }), - ]); - member.roles = member.roles.filter((x) => x.id !== role_id); + static async removeRole(user_id: string, guild_id: string, role_id: string) { + const [member] = await Promise.all([ + Member.findOneOrFail({ + where: { id: user_id, guild_id }, + relations: ["user", "roles"], // we don't want to load the role objects just the ids + select: { + index: true, + roles: { + id: true, + }, + }, + }), + Role.findOneOrFail({ where: { id: role_id, guild_id } }), + ]); + member.roles = member.roles.filter((x) => x.id !== role_id); - await Promise.all([ - member.save(), - emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id, - user: member.user, - roles: member.roles.map((x) => x.id), - }, - guild_id, - } as GuildMemberUpdateEvent), - ]); - } + await Promise.all([ + member.save(), + emitEvent({ + event: "GUILD_MEMBER_UPDATE", + data: { + guild_id, + user: member.user, + roles: member.roles.map((x) => x.id), + }, + guild_id, + } as GuildMemberUpdateEvent), + ]); + } - static async changeNickname(user_id: string, guild_id: string, nickname: string) { - const member = await Member.findOneOrFail({ - where: { - id: user_id, - guild_id, - }, - relations: ["user"], - }); + static async changeNickname(user_id: string, guild_id: string, nickname: string) { + const member = await Member.findOneOrFail({ + where: { + id: user_id, + guild_id, + }, + relations: ["user"], + }); - // @ts-expect-error Member nickname is nullable - member.nick = nickname || null; + // @ts-expect-error Member nickname is nullable + member.nick = nickname || null; - await Promise.all([ - member.save(), + await Promise.all([ + member.save(), - emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id, - user: member.user, - nick: nickname || null, - }, - guild_id, - } as GuildMemberUpdateEvent), - ]); - } + emitEvent({ + event: "GUILD_MEMBER_UPDATE", + data: { + guild_id, + user: member.user, + nick: nickname || null, + }, + guild_id, + } as GuildMemberUpdateEvent), + ]); + } - static async addToGuild(user_id: string, guild_id: string) { - const user = await User.getPublicUser(user_id); - const isBanned = await Ban.count({ where: { guild_id, user_id } }); - if (isBanned) { - throw DiscordApiErrors.USER_BANNED; - } - const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ where: { id: user_id } }); - if (guild_count >= maxGuilds) { - throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); - } + static async addToGuild(user_id: string, guild_id: string) { + const user = await User.getPublicUser(user_id); + const isBanned = await Ban.count({ where: { guild_id, user_id } }); + if (isBanned) { + throw DiscordApiErrors.USER_BANNED; + } + const { maxGuilds } = Config.get().limits.user; + const guild_count = await Member.count({ where: { id: user_id } }); + if (guild_count >= maxGuilds) { + throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); + } - const guild = await Guild.findOneOrFail({ - where: { - id: guild_id, - }, - relations: PublicGuildRelations, - relationLoadStrategy: "query", - }); + const guild = await Guild.findOneOrFail({ + where: { + id: guild_id, + }, + relations: PublicGuildRelations, + relationLoadStrategy: "query", + }); - for await (const channel of guild.channels) { - channel.position = await Channel.calculatePosition(channel.id, guild_id); - } + for await (const channel of guild.channels) { + channel.position = await Channel.calculatePosition(channel.id, guild_id); + } - const memberCount = await Member.count({ where: { guild_id } }); + const memberCount = await Member.count({ where: { guild_id } }); - const memberPreview = ( - await Member.find({ - where: { - guild_id, - user: { - sessions: { - status: Not("invisible" as const), // lol typescript? - }, - }, - }, - relations: ["user", "roles"], - take: 10, - }) - ).map((member) => member.toPublicMember()); + const memberPreview = ( + await Member.find({ + where: { + guild_id, + user: { + sessions: { + status: Not("invisible" as const), // lol typescript? + }, + }, + }, + relations: ["user", "roles"], + take: 10, + }) + ).map((member) => member.toPublicMember()); - if ( - await Member.count({ - where: { id: user.id, guild: { id: guild_id } }, - }) - ) - throw new HTTPError("You are already a member of this guild", 400); + if ( + await Member.count({ + where: { id: user.id, guild: { id: guild_id } }, + }) + ) + throw new HTTPError("You are already a member of this guild", 400); - const member = { - id: user_id, - guild_id, - nick: undefined, - roles: [guild_id], // @everyone role - joined_at: new Date(), - deaf: false, - mute: false, - pending: false, - bio: "", - }; + const member = { + id: user_id, + guild_id, + nick: undefined, + roles: [guild_id], // @everyone role + joined_at: new Date(), + deaf: false, + mute: false, + pending: false, + bio: "", + }; - await Promise.all([ - Member.create({ - ...member, - roles: [Role.create({ id: guild_id })], - // read_state: {}, - settings: { - guild_id: null, - mute_config: null, - mute_scheduled_events: false, - flags: 0, - hide_muted_channels: false, - notify_highlights: 0, - channel_overrides: {}, - message_notifications: guild.default_message_notifications, - mobile_push: true, - muted: false, - suppress_everyone: false, - suppress_roles: false, - version: 0, - }, - // Member.save is needed because else the roles relations wouldn't be updated - }).save(), - Guild.increment({ id: guild_id }, "member_count", 1), - emitEvent({ - event: "GUILD_MEMBER_ADD", - data: { - ...member, - user: user.toPublicUser(), - guild_id, - }, - guild_id, - origin: "util/entities/Member.ts:377/addToGuild(user_id, guild_id)", - } as GuildMemberAddEvent), - emitEvent({ - event: "GUILD_CREATE", - data: { - ...new ReadyGuildDTO(guild).toJSON(), - members: [...memberPreview, { ...member, user }], - member_count: memberCount + 1, - guild_hashes: {}, - guild_scheduled_events: [], - joined_at: member.joined_at, - presences: [], - stage_instances: [], - threads: [], - embedded_activities: [], - voice_states: guild.voice_states, - }, - user_id, - } as GuildCreateEvent), - ]); + await Promise.all([ + Member.create({ + ...member, + roles: [Role.create({ id: guild_id })], + // read_state: {}, + settings: { + guild_id: null, + mute_config: null, + mute_scheduled_events: false, + flags: 0, + hide_muted_channels: false, + notify_highlights: 0, + channel_overrides: {}, + message_notifications: guild.default_message_notifications, + mobile_push: true, + muted: false, + suppress_everyone: false, + suppress_roles: false, + version: 0, + }, + // Member.save is needed because else the roles relations wouldn't be updated + }).save(), + Guild.increment({ id: guild_id }, "member_count", 1), + emitEvent({ + event: "GUILD_MEMBER_ADD", + data: { + ...member, + user: user.toPublicUser(), + guild_id, + }, + guild_id, + origin: "util/entities/Member.ts:377/addToGuild(user_id, guild_id)", + } as GuildMemberAddEvent), + emitEvent({ + event: "GUILD_CREATE", + data: { + ...new ReadyGuildDTO(guild).toJSON(), + members: [...memberPreview, { ...member, user }], + member_count: memberCount + 1, + guild_hashes: {}, + guild_scheduled_events: [], + joined_at: member.joined_at, + presences: [], + stage_instances: [], + threads: [], + embedded_activities: [], + voice_states: guild.voice_states, + }, + user_id, + } as GuildCreateEvent), + ]); - if (guild.system_channel_id) { - const channel = await Channel.findOneOrFail({ - where: { id: guild.system_channel_id }, - }); - // Send a welcome message - const message = Message.create({ - type: 7, - guild_id: guild.id, - channel_id: guild.system_channel_id, - author: user, - timestamp: new Date(), - reactions: [], - attachments: [], - embeds: [], - sticker_items: [], - edited_timestamp: undefined, - mentions: [], - mention_channels: [], - mention_roles: [], - mention_everyone: false, - }); + if (guild.system_channel_id) { + const channel = await Channel.findOneOrFail({ + where: { id: guild.system_channel_id }, + }); + // Send a welcome message + const message = Message.create({ + type: 7, + guild_id: guild.id, + channel_id: guild.system_channel_id, + author: user, + timestamp: new Date(), + reactions: [], + attachments: [], + embeds: [], + sticker_items: [], + edited_timestamp: undefined, + mentions: [], + mention_channels: [], + mention_roles: [], + mention_everyone: false, + }); - channel.last_message_id = message.id; + channel.last_message_id = message.id; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: message.channel_id, - data: message, - } as MessageCreateEvent), - channel.save(), - ]); - } - } + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: message.channel_id, + data: message, + } as MessageCreateEvent), + channel.save(), + ]); + } + } - toPublicMember() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const member: any = {}; - PublicMemberProjection.forEach((x) => { - member[x] = this[x]; - }); + toPublicMember() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const member: any = {}; + PublicMemberProjection.forEach((x) => { + member[x] = this[x]; + }); - if (this.roles) member.roles = this.roles.map((x: Role) => x.id); - if (this.user) member.user = this.user.toPublicUser(); + if (this.roles) member.roles = this.roles.map((x: Role) => x.id); + if (this.user) member.user = this.user.toPublicUser(); - return member as PublicMember; - } + return member as PublicMember; + } } diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts index 79a66b8..9935ae0 100644 --- a/src/util/entities/Message.ts +++ b/src/util/entities/Message.ts @@ -33,309 +33,309 @@ import { MessageFlags } from "@spacebar/util"; @Entity({ - name: "messages", + name: "messages", }) @Index(["channel_id", "id"], { unique: true }) export class Message extends BaseClass { - @Column({ nullable: true }) - @RelationId((message: Message) => message.channel) - @Index() - channel_id?: string; + @Column({ nullable: true }) + @RelationId((message: Message) => message.channel) + @Index() + channel_id?: string; - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - channel: Channel; + @JoinColumn({ name: "channel_id" }) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) + channel: Channel; - @Column({ nullable: true }) - @RelationId((message: Message) => message.guild) - guild_id?: string; + @Column({ nullable: true }) + @RelationId((message: Message) => message.guild) + guild_id?: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { - onDelete: "CASCADE", - }) - guild?: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) + guild?: Guild; - @Column({ nullable: true }) - @RelationId((message: Message) => message.author) - @Index() - author_id?: string; + @Column({ nullable: true }) + @RelationId((message: Message) => message.author) + @Index() + author_id?: string; - @JoinColumn({ name: "author_id", referencedColumnName: "id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - author?: User; + @JoinColumn({ name: "author_id", referencedColumnName: "id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + author?: User; - @Column({ nullable: true }) - @RelationId((message: Message) => message.member) - member_id?: string; + @Column({ nullable: true }) + @RelationId((message: Message) => message.member) + member_id?: string; - @JoinColumn({ name: "member_id", referencedColumnName: "id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - member?: Member; + @JoinColumn({ name: "member_id", referencedColumnName: "id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + member?: Member; - @Column({ nullable: true }) - @RelationId((message: Message) => message.webhook) - webhook_id?: string; + @Column({ nullable: true }) + @RelationId((message: Message) => message.webhook) + webhook_id?: string; - @JoinColumn({ name: "webhook_id" }) - @ManyToOne(() => Webhook) - webhook?: Webhook; + @JoinColumn({ name: "webhook_id" }) + @ManyToOne(() => Webhook) + webhook?: Webhook; - @Column({ nullable: true }) - @RelationId((message: Message) => message.application) - application_id?: string; + @Column({ nullable: true }) + @RelationId((message: Message) => message.application) + application_id?: string; - @JoinColumn({ name: "application_id" }) - @ManyToOne(() => Application) - application?: Application; + @JoinColumn({ name: "application_id" }) + @ManyToOne(() => Application) + application?: Application; - @Column({ nullable: true }) - content?: string; + @Column({ nullable: true }) + content?: string; - @Column() - @CreateDateColumn() - timestamp: Date; + @Column() + @CreateDateColumn() + timestamp: Date; - @Column({ nullable: true }) - edited_timestamp?: Date; + @Column({ nullable: true }) + edited_timestamp?: Date; - @Column({ nullable: true }) - tts?: boolean; + @Column({ nullable: true }) + tts?: boolean; - @Column({ nullable: true }) - mention_everyone?: boolean; + @Column({ nullable: true }) + mention_everyone?: boolean; - @JoinTable({ name: "message_user_mentions" }) - @ManyToMany(() => User) - mentions: User[]; + @JoinTable({ name: "message_user_mentions" }) + @ManyToMany(() => User) + mentions: User[]; - @JoinTable({ name: "message_role_mentions" }) - @ManyToMany(() => Role) - mention_roles: Role[]; + @JoinTable({ name: "message_role_mentions" }) + @ManyToMany(() => Role) + mention_roles: Role[]; - @JoinTable({ name: "message_channel_mentions" }) - @ManyToMany(() => Channel) - mention_channels: Channel[]; + @JoinTable({ name: "message_channel_mentions" }) + @ManyToMany(() => Channel) + mention_channels: Channel[]; - @JoinTable({ name: "message_stickers" }) - @ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" }) - sticker_items?: Sticker[]; + @JoinTable({ name: "message_stickers" }) + @ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" }) + sticker_items?: Sticker[]; - @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { - cascade: true, - orphanedRowAction: "delete", - }) - attachments?: Attachment[]; + @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { + cascade: true, + orphanedRowAction: "delete", + }) + attachments?: Attachment[]; - @Column({ type: "simple-json" }) - embeds: Embed[]; + @Column({ type: "simple-json" }) + embeds: Embed[]; - @Column({ type: "simple-json" }) - reactions: Reaction[]; + @Column({ type: "simple-json" }) + reactions: Reaction[]; - @Column({ type: "text", nullable: true }) - nonce?: string; + @Column({ type: "text", nullable: true }) + nonce?: string; - @Column({ nullable: true, type: Date }) - pinned_at?: Date | null; + @Column({ nullable: true, type: Date }) + pinned_at?: Date | null; - get pinned(): boolean { - return this.pinned_at != null; - } + get pinned(): boolean { + return this.pinned_at != null; + } - @Column({ type: "int" }) - type: MessageType; + @Column({ type: "int" }) + type: MessageType; - @Column({ type: "simple-json", nullable: true }) - activity?: { - type: number; - party_id: string; - }; + @Column({ type: "simple-json", nullable: true }) + activity?: { + type: number; + party_id: string; + }; - @Column({ default: 0 }) - flags: number; + @Column({ default: 0 }) + flags: number; - @Column({ type: "simple-json", nullable: true }) - message_reference?: { - message_id: string; - channel_id?: string; - guild_id?: string; - type?: number; // 0 = DEFAULT, 1 = FORWARD - }; + @Column({ type: "simple-json", nullable: true }) + message_reference?: { + message_id: string; + channel_id?: string; + guild_id?: string; + type?: number; // 0 = DEFAULT, 1 = FORWARD + }; - @JoinColumn({ name: "message_reference_id" }) - @ManyToOne(() => Message, { onDelete: "SET NULL" }) - referenced_message?: Message | null; + @JoinColumn({ name: "message_reference_id" }) + @ManyToOne(() => Message, { onDelete: "SET NULL" }) + referenced_message?: Message | null; - @Column({ type: "simple-json", nullable: true }) - interaction?: { - id: string; - type: InteractionType; - name: string; - }; + @Column({ type: "simple-json", nullable: true }) + interaction?: { + id: string; + type: InteractionType; + name: string; + }; - @Column({ type: "simple-json", nullable: true }) - interaction_metadata?: { - id: string; - type: InteractionType; - user_id: string; - authorizing_integration_owners: object; - name: string; - command_type: ApplicationCommandType; - }; + @Column({ type: "simple-json", nullable: true }) + interaction_metadata?: { + id: string; + type: InteractionType; + user_id: string; + authorizing_integration_owners: object; + name: string; + command_type: ApplicationCommandType; + }; - @Column({ type: "simple-json", nullable: true }) - components?: ActionRowComponent[]; + @Column({ type: "simple-json", nullable: true }) + components?: ActionRowComponent[]; - @Column({ type: "simple-json", nullable: true }) - poll?: Poll; + @Column({ type: "simple-json", nullable: true }) + poll?: Poll; - @Column({ nullable: true }) - username?: string; + @Column({ nullable: true }) + username?: string; - @Column({ nullable: true }) - avatar?: string; + @Column({ nullable: true }) + avatar?: string; - toJSON(): Message { - return { - ...this, - author_id: undefined, - member_id: undefined, - webhook_id: this.webhook_id ?? undefined, - application_id: undefined, + toJSON(): Message { + return { + ...this, + author_id: undefined, + member_id: undefined, + webhook_id: this.webhook_id ?? undefined, + application_id: undefined, - nonce: this.nonce ?? undefined, - tts: this.tts ?? false, - guild: this.guild ?? undefined, - webhook: this.webhook ?? undefined, - interaction: this.interaction ?? undefined, - interaction_metadata: this.interaction_metadata ?? undefined, - reactions: this.reactions ?? undefined, - sticker_items: this.sticker_items ?? undefined, - message_reference: this.message_reference ?? undefined, - author: { - ...(this.author?.toPublicUser() ?? undefined), - // Webhooks - username: this.username ?? this.author?.username, - avatar: this.avatar ?? this.author?.avatar, - }, - activity: this.activity ?? undefined, - application: this.application ?? undefined, - components: this.components ?? undefined, - poll: this.poll ?? undefined, - content: this.content ?? "", - pinned: this.pinned, - }; - } + nonce: this.nonce ?? undefined, + tts: this.tts ?? false, + guild: this.guild ?? undefined, + webhook: this.webhook ?? undefined, + interaction: this.interaction ?? undefined, + interaction_metadata: this.interaction_metadata ?? undefined, + reactions: this.reactions ?? undefined, + sticker_items: this.sticker_items ?? undefined, + message_reference: this.message_reference ?? undefined, + author: { + ...(this.author?.toPublicUser() ?? undefined), + // Webhooks + username: this.username ?? this.author?.username, + avatar: this.avatar ?? this.author?.avatar, + }, + activity: this.activity ?? undefined, + application: this.application ?? undefined, + components: this.components ?? undefined, + poll: this.poll ?? undefined, + content: this.content ?? "", + pinned: this.pinned, + }; + } - toPartialMessage(): PartialMessage { - return { - id: this.id, - // lobby_id: this.lobby_id, - channel_id: this.channel_id!, - type: this.type, - content: this.content!, - author: { ...this.author!, avatar: this.author?.avatar ?? null }, - flags: this.flags, - application_id: this.application_id, - //channel: this.channel, // TODO: ephemeral DM channels - // recipient_id: this.recipient_id, // TODO: ephemeral DM channels - }; - } + toPartialMessage(): PartialMessage { + return { + id: this.id, + // lobby_id: this.lobby_id, + channel_id: this.channel_id!, + type: this.type, + content: this.content!, + author: { ...this.author!, avatar: this.author?.avatar ?? null }, + flags: this.flags, + application_id: this.application_id, + //channel: this.channel, // TODO: ephemeral DM channels + // recipient_id: this.recipient_id, // TODO: ephemeral DM channels + }; + } - withSignedAttachments(data: NewUrlUserSignatureData) { - return { - ...this, - attachments: this.attachments?.map((attachment: Attachment) => Attachment.prototype.signUrls.call(attachment, data)), - }; - } + withSignedAttachments(data: NewUrlUserSignatureData) { + return { + ...this, + attachments: this.attachments?.map((attachment: Attachment) => Attachment.prototype.signUrls.call(attachment, data)), + }; + } - static async createWithDefaults(opts: Partial): Promise { - const message = Message.create(); + static async createWithDefaults(opts: Partial): Promise { + const message = Message.create(); - if (!opts.author) { - if (!opts.author_id) throw new Error("Either author or author_id must be provided to create a Message"); - opts.author = await User.findOneOrFail({ where: { id: opts.author_id! } }); - } + if (!opts.author) { + if (!opts.author_id) throw new Error("Either author or author_id must be provided to create a Message"); + opts.author = await User.findOneOrFail({ where: { id: opts.author_id! } }); + } - if (!opts.channel) { - if (!opts.channel_id) throw new Error("Either channel or channel_id must be provided to create a Message"); - opts.channel = await Channel.findOneOrFail({ where: { id: opts.channel_id! } }); - opts.guild_id ??= opts.channel.guild_id; - } + if (!opts.channel) { + if (!opts.channel_id) throw new Error("Either channel or channel_id must be provided to create a Message"); + opts.channel = await Channel.findOneOrFail({ where: { id: opts.channel_id! } }); + opts.guild_id ??= opts.channel.guild_id; + } - if (!opts.member_id) opts.member_id = message.author_id; - if (!opts.member) opts.member = await Member.findOneOrFail({ where: { id: opts.member_id! } }); + if (!opts.member_id) opts.member_id = message.author_id; + if (!opts.member) opts.member = await Member.findOneOrFail({ where: { id: opts.member_id! } }); - if (!opts.guild) { - if (opts.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.guild_id! } }); - else if (opts.channel?.guild?.id) opts.guild = opts.channel.guild; - else if (opts.channel?.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.channel.guild_id! } }); - else if (opts.member?.guild?.id) opts.guild = opts.member.guild; - else if (opts.member?.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.member.guild_id! } }); - else throw new Error("Either guild, guild_id, channel.guild, channel.guild_id, member.guild or member.guild_id must be provided to create a Message"); - } + if (!opts.guild) { + if (opts.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.guild_id! } }); + else if (opts.channel?.guild?.id) opts.guild = opts.channel.guild; + else if (opts.channel?.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.channel.guild_id! } }); + else if (opts.member?.guild?.id) opts.guild = opts.member.guild; + else if (opts.member?.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.member.guild_id! } }); + else throw new Error("Either guild, guild_id, channel.guild, channel.guild_id, member.guild or member.guild_id must be provided to create a Message"); + } - // try 2 now that we have a guild - if (!opts.member) opts.member = await Member.findOneOrFail({ where: { id: opts.author!.id, guild_id: opts.guild!.id } }); + // try 2 now that we have a guild + if (!opts.member) opts.member = await Member.findOneOrFail({ where: { id: opts.author!.id, guild_id: opts.guild!.id } }); - // set reply type if a message if referenced - if (opts.message_reference && !opts.type) message.type = MessageType.REPLY; + // set reply type if a message if referenced + if (opts.message_reference && !opts.type) message.type = MessageType.REPLY; - // backpropagate ids - opts.channel_id = opts.channel.id; - opts.guild_id = opts.guild.id; - opts.author_id = opts.author.id; - opts.member_id = opts.member.id; - opts.webhook_id = opts.webhook?.id; - opts.application_id = opts.application?.id; + // backpropagate ids + opts.channel_id = opts.channel.id; + opts.guild_id = opts.guild.id; + opts.author_id = opts.author.id; + opts.member_id = opts.member.id; + opts.webhook_id = opts.webhook?.id; + opts.application_id = opts.application?.id; - delete opts.member; + delete opts.member; - Object.assign(message, { - tts: false, - embeds: [], - reactions: [], - flags: 0, - type: 0, - timestamp: new Date(), - ...opts, - }); - return message; - } - static addDefault(options: FindOneOptions) { - if (options.where) { - const arr = options.where instanceof Array ? options.where : [options.where]; - for (const thing of arr) { - if (!("flags" in thing)) { - thing.flags = Not(Raw((alias) => `${alias} & ${MessageFlags.FLAGS.EPHEMERAL} = ${MessageFlags.FLAGS.EPHEMERAL}`)); - } - } - } - } + Object.assign(message, { + tts: false, + embeds: [], + reactions: [], + flags: 0, + type: 0, + timestamp: new Date(), + ...opts, + }); + return message; + } + static addDefault(options: FindOneOptions) { + if (options.where) { + const arr = options.where instanceof Array ? options.where : [options.where]; + for (const thing of arr) { + if (!("flags" in thing)) { + thing.flags = Not(Raw((alias) => `${alias} & ${MessageFlags.FLAGS.EPHEMERAL} = ${MessageFlags.FLAGS.EPHEMERAL}`)); + } + } + } + } } //@ts-expect-error It works but TS types hate it Message.findOneOrFail = function (this: Message, options: FindOneOptions): Promise { - Message.addDefault(options as FindOneOptions); - //@ts-expect-error how to use generics on call, who knows! - return BaseEntity.findOneOrFail.call(Message, options); + Message.addDefault(options as FindOneOptions); + //@ts-expect-error how to use generics on call, who knows! + return BaseEntity.findOneOrFail.call(Message, options); }; //@ts-expect-error It works but TS types hate it Message.findOne = function (this: Message, options: FindOneOptions): Promise { - Message.addDefault(options as FindOneOptions); - //@ts-expect-error how to use generics on call, who knows! - return BaseEntity.findOne.call(Message, options); + Message.addDefault(options as FindOneOptions); + //@ts-expect-error how to use generics on call, who knows! + return BaseEntity.findOne.call(Message, options); }; //@ts-expect-error It works but TS types hate it Message.find = function (this: Message, options: FindOneOptions): Promise { - Message.addDefault(options as FindOneOptions); - //@ts-expect-error how to use generics on call, who knows! - return BaseEntity.find.call(Message, options); + Message.addDefault(options as FindOneOptions); + //@ts-expect-error how to use generics on call, who knows! + return BaseEntity.find.call(Message, options); }; diff --git a/src/util/entities/Migration.ts b/src/util/entities/Migration.ts index 128664f..878a404 100644 --- a/src/util/entities/Migration.ts +++ b/src/util/entities/Migration.ts @@ -21,15 +21,15 @@ export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryGeneratedColumn; @Entity({ - name: "migrations", + name: "migrations", }) export class Migration extends BaseEntity { - @PrimaryIdAutoGenerated() - id: number; + @PrimaryIdAutoGenerated() + id: number; - @Column({ type: "bigint" }) - timestamp: number; + @Column({ type: "bigint" }) + timestamp: number; - @Column() - name: string; + @Column() + name: string; } diff --git a/src/util/entities/Note.ts b/src/util/entities/Note.ts index c1b1252..e9f173f 100644 --- a/src/util/entities/Note.ts +++ b/src/util/entities/Note.ts @@ -21,18 +21,18 @@ import { User } from "./User"; @Entity({ - name: "notes", + name: "notes", }) @Unique(["owner", "target"]) export class Note extends BaseClass { - @JoinColumn({ name: "owner_id" }) - @ManyToOne(() => User, { onDelete: "CASCADE" }) - owner: User; + @JoinColumn({ name: "owner_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + owner: User; - @JoinColumn({ name: "target_id" }) - @ManyToOne(() => User, { onDelete: "CASCADE" }) - target: User; + @JoinColumn({ name: "target_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + target: User; - @Column() - content: string; + @Column() + content: string; } diff --git a/src/util/entities/RateLimit.ts b/src/util/entities/RateLimit.ts index 04c8abb..ab85285 100644 --- a/src/util/entities/RateLimit.ts +++ b/src/util/entities/RateLimit.ts @@ -20,18 +20,18 @@ import { BaseClass } from "./BaseClass"; @Entity({ - name: "rate_limits", + name: "rate_limits", }) export class RateLimit extends BaseClass { - @Column() // no relation as it also - executor_id: string; + @Column() // no relation as it also + executor_id: string; - @Column() - hits: number; + @Column() + hits: number; - @Column() - blocked: boolean; + @Column() + blocked: boolean; - @Column() - expires_at: Date; + @Column() + expires_at: Date; } diff --git a/src/util/entities/ReadState.ts b/src/util/entities/ReadState.ts index be3de5d..e334cd7 100644 --- a/src/util/entities/ReadState.ts +++ b/src/util/entities/ReadState.ts @@ -26,49 +26,49 @@ // public read receipt ≥ notification cursor ≥ private fully read marker @Entity({ - name: "read_states", + name: "read_states", }) @Index(["channel_id", "user_id"], { unique: true }) export class ReadState extends BaseClass { - @Column() - @RelationId((read_state: ReadState) => read_state.channel) - channel_id: string; + @Column() + @RelationId((read_state: ReadState) => read_state.channel) + channel_id: string; - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - channel: Channel; + @JoinColumn({ name: "channel_id" }) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) + channel: Channel; - @Column() - @RelationId((read_state: ReadState) => read_state.user) - user_id: string; + @Column() + @RelationId((read_state: ReadState) => read_state.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - // fully read marker - @Column({ nullable: true }) - last_message_id: string; + // fully read marker + @Column({ nullable: true }) + last_message_id: string; - // public read receipt - @Column({ nullable: true }) - public_ack: string; + // public read receipt + @Column({ nullable: true }) + public_ack: string; - // notification cursor / private read receipt - @Column({ nullable: true }) - notifications_cursor: string; + // notification cursor / private read receipt + @Column({ nullable: true }) + notifications_cursor: string; - @Column({ nullable: true }) - last_pin_timestamp?: Date; + @Column({ nullable: true }) + last_pin_timestamp?: Date; - @Column({ nullable: true }) - mention_count: number; + @Column({ nullable: true }) + mention_count: number; - // @Column({ nullable: true }) - // TODO: derive this from (last_message_id=notifications_cursor=public_ack)=true - manual: boolean; + // @Column({ nullable: true }) + // TODO: derive this from (last_message_id=notifications_cursor=public_ack)=true + manual: boolean; } diff --git a/src/util/entities/Recipient.ts b/src/util/entities/Recipient.ts index 37a2cb4..2dcff24 100644 --- a/src/util/entities/Recipient.ts +++ b/src/util/entities/Recipient.ts @@ -20,31 +20,31 @@ import { BaseClass } from "./BaseClass"; @Entity({ - name: "recipients", + name: "recipients", }) export class Recipient extends BaseClass { - @Column() - @RelationId((recipient: Recipient) => recipient.channel) - channel_id: string; + @Column() + @RelationId((recipient: Recipient) => recipient.channel) + channel_id: string; - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => require("./Channel").Channel, { - onDelete: "CASCADE", - }) - channel: import("./Channel").Channel; + @JoinColumn({ name: "channel_id" }) + @ManyToOne(() => require("./Channel").Channel, { + onDelete: "CASCADE", + }) + channel: import("./Channel").Channel; - @Column() - @RelationId((recipient: Recipient) => recipient.user) - user_id: string; + @Column() + @RelationId((recipient: Recipient) => recipient.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => require("./User").User, { - onDelete: "CASCADE", - }) - user: import("./User").User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => require("./User").User, { + onDelete: "CASCADE", + }) + user: import("./User").User; - @Column({ default: false }) - closed: boolean; + @Column({ default: false }) + closed: boolean; - // TODO: settings/mute/nick/added at/encryption keys/read_state + // TODO: settings/mute/nick/added at/encryption keys/read_state } diff --git a/src/util/entities/Relationship.ts b/src/util/entities/Relationship.ts index 8fc83e7..c2256ed 100644 --- a/src/util/entities/Relationship.ts +++ b/src/util/entities/Relationship.ts @@ -22,42 +22,42 @@ import { RelationshipType } from "@spacebar/schemas"; @Entity({ - name: "relationships", + name: "relationships", }) @Index(["from_id", "to_id"], { unique: true }) export class Relationship extends BaseClass { - @Column({}) - @RelationId((relationship: Relationship) => relationship.from) - from_id: string; + @Column({}) + @RelationId((relationship: Relationship) => relationship.from) + from_id: string; - @JoinColumn({ name: "from_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - from: User; + @JoinColumn({ name: "from_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + from: User; - @Column({}) - @RelationId((relationship: Relationship) => relationship.to) - to_id: string; + @Column({}) + @RelationId((relationship: Relationship) => relationship.to) + to_id: string; - @JoinColumn({ name: "to_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - to: User; + @JoinColumn({ name: "to_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + to: User; - @Column({ nullable: true }) - nickname?: string; + @Column({ nullable: true }) + nickname?: string; - @Column({ type: "int" }) - type: RelationshipType; + @Column({ type: "int" }) + type: RelationshipType; - toPublicRelationship() { - return { - id: this.to?.id || this.to_id, - type: this.type, - nickname: this.nickname, - user: this.to?.toPublicUser(), - }; - } + toPublicRelationship() { + return { + id: this.to?.id || this.to_id, + type: this.type, + nickname: this.nickname, + user: this.to?.toPublicUser(), + }; + } } diff --git a/src/util/entities/Role.ts b/src/util/entities/Role.ts index 8ee3605..7908f42 100644 --- a/src/util/entities/Role.ts +++ b/src/util/entities/Role.ts @@ -23,63 +23,63 @@ import { RoleColors } from "@spacebar/schemas"; @Entity({ - name: "roles", + name: "roles", }) export class Role extends BaseClass { - @Column() - @RelationId((role: Role) => role.guild) - guild_id: string; + @Column() + @RelationId((role: Role) => role.guild) + guild_id: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild) => guild.roles, { - onDelete: "CASCADE", - }) - guild: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, (guild) => guild.roles, { + onDelete: "CASCADE", + }) + guild: Guild; - @Column() - color: number; + @Column() + color: number; - @Column() - hoist: boolean; + @Column() + hoist: boolean; - @Column() - managed: boolean; + @Column() + managed: boolean; - @Column() - mentionable: boolean; + @Column() + mentionable: boolean; - @Column() - name: string; + @Column() + name: string; - @Column() - permissions: string; + @Column() + permissions: string; - @Column() - position: number; + @Column() + position: number; - @Column({ nullable: true }) - icon?: string; + @Column({ nullable: true }) + icon?: string; - @Column({ nullable: true }) - unicode_emoji?: string; + @Column({ nullable: true }) + unicode_emoji?: string; - @Column({ type: "simple-json", nullable: true }) - tags?: { - bot_id?: string; - integration_id?: string; - premium_subscriber?: boolean; - }; + @Column({ type: "simple-json", nullable: true }) + tags?: { + bot_id?: string; + integration_id?: string; + premium_subscriber?: boolean; + }; - @Column({ default: 0 }) - flags: number; + @Column({ default: 0 }) + flags: number; - @Column({ nullable: false, type: "simple-json" }) - colors: RoleColors; + @Column({ nullable: false, type: "simple-json" }) + colors: RoleColors; - toJSON(): Role { - return { - ...this, - tags: this.tags ?? undefined, - }; - } + toJSON(): Role { + return { + ...this, + tags: this.tags ?? undefined, + }; + } } diff --git a/src/util/entities/SecurityKey.ts b/src/util/entities/SecurityKey.ts index 94adbbc..cbf80d3 100644 --- a/src/util/entities/SecurityKey.ts +++ b/src/util/entities/SecurityKey.ts @@ -21,28 +21,28 @@ import { User } from "./User"; @Entity({ - name: "security_keys", + name: "security_keys", }) export class SecurityKey extends BaseClass { - @Column({ nullable: true }) - @RelationId((key: SecurityKey) => key.user) - user_id: string; + @Column({ nullable: true }) + @RelationId((key: SecurityKey) => key.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - @Column() - key_id: string; + @Column() + key_id: string; - @Column() - public_key: string; + @Column() + public_key: string; - @Column() - counter: number; + @Column() + counter: number; - @Column() - name: string; + @Column() + name: string; } diff --git a/src/util/entities/Session.ts b/src/util/entities/Session.ts index db6b1d2..585b2ec 100644 --- a/src/util/entities/Session.ts +++ b/src/util/entities/Session.ts @@ -28,157 +28,157 @@ //TODO we need to remove all sessions on server start because if the server crashes without closing websockets it won't delete them @Entity({ - name: "sessions", + name: "sessions", }) export class Session extends BaseClassWithoutId { - @PrimaryColumn({ nullable: false }) - session_id: string = randomUpperString(); + @PrimaryColumn({ nullable: false }) + session_id: string = randomUpperString(); - @Column() - @RelationId((session: Session) => session.user) - @Index({}) - user_id: string; + @Column() + @RelationId((session: Session) => session.user) + @Index({}) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - @Column({ type: "simple-json", default: "[]" }) - activities: Activity[]; + @Column({ type: "simple-json", default: "[]" }) + activities: Activity[]; - @Column({ type: "simple-json" }) - client_info: { - platform?: string; - os?: string; - version?: number; - location?: string; - }; + @Column({ type: "simple-json" }) + client_info: { + platform?: string; + os?: string; + version?: number; + location?: string; + }; - @Column({ type: "simple-json" }) - client_status: ClientStatus; + @Column({ type: "simple-json" }) + client_status: ClientStatus; - @Column({ nullable: false, type: String }) - status: Status; //TODO enum + @Column({ nullable: false, type: String }) + status: Status; //TODO enum - @Column({ default: false }) - is_admin_session: boolean; + @Column({ default: false }) + is_admin_session: boolean; - @CreateDateColumn({ type: Date }) - created_at: Date; + @CreateDateColumn({ type: Date }) + created_at: Date; - @Column({ nullable: true, type: Date }) - last_seen?: Date; + @Column({ nullable: true, type: Date }) + last_seen?: Date; - @Column({ nullable: true, type: String }) - last_seen_ip?: string; + @Column({ nullable: true, type: String }) + last_seen_ip?: string; - @Column({ nullable: true, type: String }) - last_seen_location?: string; + @Column({ nullable: true, type: String }) + last_seen_location?: string; - @Column({ nullable: true, type: "simple-json" }) - last_seen_location_info?: ExtendedLocationInfo; + @Column({ nullable: true, type: "simple-json" }) + last_seen_location_info?: ExtendedLocationInfo; - @Column({ nullable: true, type: String }) - session_nickname?: string; + @Column({ nullable: true, type: String }) + session_nickname?: string; - getPublicStatus() { - return this.status === "invisible" ? "offline" : this.status; - } + getPublicStatus() { + return this.status === "invisible" ? "offline" : this.status; + } - getDiscordDeviceInfo() { - return { - id_hash: crypto.createHash("sha256").update(this.session_id).digest("hex"), - approx_last_used_time: (this.last_seen ?? new Date(0)).toISOString(), - client_info: { - os: this.client_info?.os, - platform: - this.client_info?.platform + (this.client_info?.version ? ` ${this.client_info?.version}` : "") + (this.session_nickname ? ` (${this.session_nickname})` : ""), - location: this.last_seen_location, - }, - }; - } + getDiscordDeviceInfo() { + return { + id_hash: crypto.createHash("sha256").update(this.session_id).digest("hex"), + approx_last_used_time: (this.last_seen ?? new Date(0)).toISOString(), + client_info: { + os: this.client_info?.os, + platform: + this.client_info?.platform + (this.client_info?.version ? ` ${this.client_info?.version}` : "") + (this.session_nickname ? ` (${this.session_nickname})` : ""), + location: this.last_seen_location, + }, + }; + } - getExtendedDeviceInfo() { - return { - id: this.session_id, - id_hash: crypto.createHash("sha256").update(this.session_id).digest("hex"), - status: this.status, - activities: this.activities, - client_status: this.client_status, - approx_last_used_time: (this.last_seen ?? new Date(0)).toISOString(), - client_info: { - ...(this.client_info ?? {}), - location: this.last_seen_location, - }, - last_seen: this.last_seen, - last_seen_ip: this.last_seen_ip, - last_seen_location: this.last_seen_location, - last_seen_location_info: this.last_seen_location_info, - }; - } + getExtendedDeviceInfo() { + return { + id: this.session_id, + id_hash: crypto.createHash("sha256").update(this.session_id).digest("hex"), + status: this.status, + activities: this.activities, + client_status: this.client_status, + approx_last_used_time: (this.last_seen ?? new Date(0)).toISOString(), + client_info: { + ...(this.client_info ?? {}), + location: this.last_seen_location, + }, + last_seen: this.last_seen, + last_seen_ip: this.last_seen_ip, + last_seen_location: this.last_seen_location, + last_seen_location_info: this.last_seen_location_info, + }; + } - toPrivateGatewayDeviceInfo(): GatewaySession { - // TODO: ... or has `show_current_game` privacy setting enabled - except spotify (always visible) - const hasPrivateActivities = this.status == "offline" || this.status == "invisible"; - const inactiveTreshold = new DateBuilder(new Date(0)).addMinutes(5).buildTimestamp(); + toPrivateGatewayDeviceInfo(): GatewaySession { + // TODO: ... or has `show_current_game` privacy setting enabled - except spotify (always visible) + const hasPrivateActivities = this.status == "offline" || this.status == "invisible"; + const inactiveTreshold = new DateBuilder(new Date(0)).addMinutes(5).buildTimestamp(); - return { - session_id: this.session_id, - client_info: { - client: this.client_info?.platform ?? "", - os: this.client_info?.os ?? "", - version: this.client_info?.version ?? 0, - } as GatewaySessionClientInfo, - status: this.status, - activities: hasPrivateActivities ? [] : this.activities, - hidden_activities: hasPrivateActivities ? this.activities : [], - active: TimeSpan.fromDates(this.last_seen?.getTime() ?? 0, new Date().getTime()).totalMillis < inactiveTreshold, - }; - } + return { + session_id: this.session_id, + client_info: { + client: this.client_info?.platform ?? "", + os: this.client_info?.os ?? "", + version: this.client_info?.version ?? 0, + } as GatewaySessionClientInfo, + status: this.status, + activities: hasPrivateActivities ? [] : this.activities, + hidden_activities: hasPrivateActivities ? this.activities : [], + active: TimeSpan.fromDates(this.last_seen?.getTime() ?? 0, new Date().getTime()).totalMillis < inactiveTreshold, + }; + } - async updateIpInfo() { - const ipInfo = await IpDataClient.getIpInfo(this.last_seen_ip!); - if (ipInfo?.ip) { - this.last_seen_location = `${ipInfo.emoji_flag} ${ipInfo.postal} ${ipInfo.city}, ${ipInfo.region}, ${ipInfo.country_name}`; - this.last_seen_location_info = { - is_eu: ipInfo.is_eu, - city: ipInfo.city, - region: ipInfo.region, - region_code: ipInfo.region_code, - country_name: ipInfo.country_name, - country_code: ipInfo.country_code, - continent_name: ipInfo.continent_name, - continent_code: ipInfo.continent_code, - latitude: ipInfo.latitude, - longitude: ipInfo.longitude, - postal: ipInfo.postal, - calling_code: ipInfo.calling_code, - flag: ipInfo.flag, - emoji_flag: ipInfo.emoji_flag, - emoji_unicode: ipInfo.emoji_unicode, - }; - } - } + async updateIpInfo() { + const ipInfo = await IpDataClient.getIpInfo(this.last_seen_ip!); + if (ipInfo?.ip) { + this.last_seen_location = `${ipInfo.emoji_flag} ${ipInfo.postal} ${ipInfo.city}, ${ipInfo.region}, ${ipInfo.country_name}`; + this.last_seen_location_info = { + is_eu: ipInfo.is_eu, + city: ipInfo.city, + region: ipInfo.region, + region_code: ipInfo.region_code, + country_name: ipInfo.country_name, + country_code: ipInfo.country_code, + continent_name: ipInfo.continent_name, + continent_code: ipInfo.continent_code, + latitude: ipInfo.latitude, + longitude: ipInfo.longitude, + postal: ipInfo.postal, + calling_code: ipInfo.calling_code, + flag: ipInfo.flag, + emoji_flag: ipInfo.emoji_flag, + emoji_unicode: ipInfo.emoji_unicode, + }; + } + } } export interface ExtendedLocationInfo { - is_eu: boolean; - city: string; - region: string; - region_code: string; - country_name: string; - country_code: string; - continent_name: string; - continent_code: string; - latitude: number; - longitude: number; - postal: string; - calling_code: string; - flag: string; - emoji_flag: string; - emoji_unicode: string; + is_eu: boolean; + city: string; + region: string; + region_code: string; + country_name: string; + country_code: string; + continent_name: string; + continent_code: string; + latitude: number; + longitude: number; + postal: string; + calling_code: string; + flag: string; + emoji_flag: string; + emoji_unicode: string; } export const PrivateSessionProjection: (keyof Session)[] = ["user_id", "session_id", "activities", "client_info", "status"]; diff --git a/src/util/entities/Sticker.ts b/src/util/entities/Sticker.ts index 07fc8d3..c88e0c2 100644 --- a/src/util/entities/Sticker.ts +++ b/src/util/entities/Sticker.ts @@ -23,53 +23,53 @@ import { StickerFormatType, StickerType } from "@spacebar/schemas"; @Entity({ - name: "stickers", + name: "stickers", }) export class Sticker extends BaseClass { - @Column() - name: string; + @Column() + name: string; - @Column({ nullable: true }) - description?: string; + @Column({ nullable: true }) + description?: string; - @Column({ nullable: true }) - available?: boolean; + @Column({ nullable: true }) + available?: boolean; - @Column({ nullable: true }) - tags?: string; + @Column({ nullable: true }) + tags?: string; - @Column({ nullable: true }) - @RelationId((sticker: Sticker) => sticker.pack) - pack_id?: string; + @Column({ nullable: true }) + @RelationId((sticker: Sticker) => sticker.pack) + pack_id?: string; - @JoinColumn({ name: "pack_id" }) - @ManyToOne(() => require("./StickerPack").StickerPack, { - onDelete: "CASCADE", - nullable: true, - }) - pack: import("./StickerPack").StickerPack; + @JoinColumn({ name: "pack_id" }) + @ManyToOne(() => require("./StickerPack").StickerPack, { + onDelete: "CASCADE", + nullable: true, + }) + pack: import("./StickerPack").StickerPack; - @Column({ nullable: true }) - guild_id?: string; + @Column({ nullable: true }) + guild_id?: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild) => guild.stickers, { - onDelete: "CASCADE", - }) - guild?: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, (guild) => guild.stickers, { + onDelete: "CASCADE", + }) + guild?: Guild; - @Column({ nullable: true }) - user_id?: string; + @Column({ nullable: true }) + user_id?: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user?: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user?: User; - @Column({ type: "int" }) - type: StickerType; + @Column({ type: "int" }) + type: StickerType; - @Column({ type: "int" }) - format_type: StickerFormatType; + @Column({ type: "int" }) + format_type: StickerFormatType; } diff --git a/src/util/entities/StickerPack.ts b/src/util/entities/StickerPack.ts index 94671cf..d4a598f 100644 --- a/src/util/entities/StickerPack.ts +++ b/src/util/entities/StickerPack.ts @@ -21,31 +21,31 @@ import { BaseClass } from "./BaseClass"; @Entity({ - name: "sticker_packs", + name: "sticker_packs", }) export class StickerPack extends BaseClass { - @Column() - name: string; + @Column() + name: string; - @Column({ nullable: true }) - description?: string; + @Column({ nullable: true }) + description?: string; - @Column({ nullable: true }) - banner_asset_id?: string; + @Column({ nullable: true }) + banner_asset_id?: string; - @OneToMany(() => Sticker, (sticker: Sticker) => sticker.pack, { - cascade: true, - orphanedRowAction: "delete", - }) - stickers: Sticker[]; + @OneToMany(() => Sticker, (sticker: Sticker) => sticker.pack, { + cascade: true, + orphanedRowAction: "delete", + }) + stickers: Sticker[]; - // sku_id: string + // sku_id: string - @Column({ nullable: true }) - @RelationId((pack: StickerPack) => pack.cover_sticker) - cover_sticker_id?: string; + @Column({ nullable: true }) + @RelationId((pack: StickerPack) => pack.cover_sticker) + cover_sticker_id?: string; - @ManyToOne(() => Sticker, { nullable: true }) - @JoinColumn() - cover_sticker?: Sticker; + @ManyToOne(() => Sticker, { nullable: true }) + @JoinColumn() + cover_sticker?: Sticker; } diff --git a/src/util/entities/Stream.ts b/src/util/entities/Stream.ts index 325d902..bf2f3a6 100644 --- a/src/util/entities/Stream.ts +++ b/src/util/entities/Stream.ts @@ -4,29 +4,29 @@ import { Channel } from "./Channel"; @Entity({ - name: "streams", + name: "streams", }) export class Stream extends BaseClass { - @Column() - @RelationId((stream: Stream) => stream.owner) - owner_id: string; + @Column() + @RelationId((stream: Stream) => stream.owner) + owner_id: string; - @JoinColumn({ name: "owner_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - owner: User; + @JoinColumn({ name: "owner_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + owner: User; - @Column() - @RelationId((stream: Stream) => stream.channel) - channel_id: string; + @Column() + @RelationId((stream: Stream) => stream.channel) + channel_id: string; - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - channel: Channel; + @JoinColumn({ name: "channel_id" }) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) + channel: Channel; - @Column() - endpoint: string; + @Column() + endpoint: string; } diff --git a/src/util/entities/StreamSession.ts b/src/util/entities/StreamSession.ts index 7579dd8..1dca242 100644 --- a/src/util/entities/StreamSession.ts +++ b/src/util/entities/StreamSession.ts @@ -4,36 +4,36 @@ import { Stream } from "./Stream"; @Entity({ - name: "stream_sessions", + name: "stream_sessions", }) export class StreamSession extends BaseClass { - @Column() - @RelationId((session: StreamSession) => session.stream) - stream_id: string; + @Column() + @RelationId((session: StreamSession) => session.stream) + stream_id: string; - @JoinColumn({ name: "stream_id" }) - @ManyToOne(() => Stream, { - onDelete: "CASCADE", - }) - stream: Stream; + @JoinColumn({ name: "stream_id" }) + @ManyToOne(() => Stream, { + onDelete: "CASCADE", + }) + stream: Stream; - @Column() - @RelationId((session: StreamSession) => session.user) - user_id: string; + @Column() + @RelationId((session: StreamSession) => session.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - @Column({ nullable: true }) - token: string; + @Column({ nullable: true }) + token: string; - // this is for gateway session - @Column() - session_id: string; + // this is for gateway session + @Column() + session_id: string; - @Column({ default: false }) - used: boolean; + @Column({ default: false }) + used: boolean; } diff --git a/src/util/entities/Team.ts b/src/util/entities/Team.ts index 08f5975..e440e96 100644 --- a/src/util/entities/Team.ts +++ b/src/util/entities/Team.ts @@ -22,26 +22,26 @@ import { User } from "./User"; @Entity({ - name: "teams", + name: "teams", }) export class Team extends BaseClass { - @Column({ nullable: true }) - icon?: string; + @Column({ nullable: true }) + icon?: string; - @JoinColumn({ name: "member_ids" }) - @OneToMany(() => TeamMember, (member: TeamMember) => member.team, { - orphanedRowAction: "delete", - }) - members: TeamMember[]; + @JoinColumn({ name: "member_ids" }) + @OneToMany(() => TeamMember, (member: TeamMember) => member.team, { + orphanedRowAction: "delete", + }) + members: TeamMember[]; - @Column() - name: string; + @Column() + name: string; - @Column({ nullable: true }) - @RelationId((team: Team) => team.owner_user) - owner_user_id: string; + @Column({ nullable: true }) + @RelationId((team: Team) => team.owner_user) + owner_user_id: string; - @JoinColumn({ name: "owner_user_id" }) - @ManyToOne(() => User) - owner_user: User; + @JoinColumn({ name: "owner_user_id" }) + @ManyToOne(() => User) + owner_user: User; } diff --git a/src/util/entities/TeamMember.ts b/src/util/entities/TeamMember.ts index 90c9061..779a811 100644 --- a/src/util/entities/TeamMember.ts +++ b/src/util/entities/TeamMember.ts @@ -22,35 +22,35 @@ import { TeamMemberRole, TeamMemberState } from "@spacebar/schemas"; @Entity({ - name: "team_members", + name: "team_members", }) export class TeamMember extends BaseClass { - @Column({ type: "int" }) - membership_state: TeamMemberState; + @Column({ type: "int" }) + membership_state: TeamMemberState; - @Column({ type: "simple-array" }) - permissions: string[]; + @Column({ type: "simple-array" }) + permissions: string[]; - @Column() - role: TeamMemberRole; + @Column() + role: TeamMemberRole; - @Column({ nullable: true }) - @RelationId((member: TeamMember) => member.team) - team_id: string; + @Column({ nullable: true }) + @RelationId((member: TeamMember) => member.team) + team_id: string; - @JoinColumn({ name: "team_id" }) - @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, { - onDelete: "CASCADE", - }) - team: import("./Team").Team; + @JoinColumn({ name: "team_id" }) + @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, { + onDelete: "CASCADE", + }) + team: import("./Team").Team; - @Column({ nullable: true }) - @RelationId((member: TeamMember) => member.user) - user_id: string; + @Column({ nullable: true }) + @RelationId((member: TeamMember) => member.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; } diff --git a/src/util/entities/Template.ts b/src/util/entities/Template.ts index bd48582..a9e341b 100644 --- a/src/util/entities/Template.ts +++ b/src/util/entities/Template.ts @@ -22,43 +22,43 @@ import { User } from "./User"; @Entity({ - name: "templates", + name: "templates", }) export class Template extends BaseClass { - @Column({ unique: true }) - code: string; + @Column({ unique: true }) + code: string; - @Column() - name: string; + @Column() + name: string; - @Column({ nullable: true }) - description?: string; + @Column({ nullable: true }) + description?: string; - @Column({ nullable: true }) - usage_count?: number; + @Column({ nullable: true }) + usage_count?: number; - @Column({ nullable: true }) - @RelationId((template: Template) => template.creator) - creator_id: string; + @Column({ nullable: true }) + @RelationId((template: Template) => template.creator) + creator_id: string; - @JoinColumn({ name: "creator_id" }) - @ManyToOne(() => User) - creator: User; + @JoinColumn({ name: "creator_id" }) + @ManyToOne(() => User) + creator: User; - @Column() - created_at: Date; + @Column() + created_at: Date; - @Column() - updated_at: Date; + @Column() + updated_at: Date; - @Column({ nullable: true }) - @RelationId((template: Template) => template.source_guild) - source_guild_id: string; + @Column({ nullable: true }) + @RelationId((template: Template) => template.source_guild) + source_guild_id: string; - @JoinColumn({ name: "source_guild_id" }) - @ManyToOne(() => Guild, { onDelete: "CASCADE" }) - source_guild: Guild; + @JoinColumn({ name: "source_guild_id" }) + @ManyToOne(() => Guild, { onDelete: "CASCADE" }) + source_guild: Guild; - @Column({ type: "simple-json" }) - serialized_source_guild: Guild; + @Column({ type: "simple-json" }) + serialized_source_guild: Guild; } diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index 375aaba..271a26c 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -30,347 +30,347 @@ import { ChannelType, PrivateUserProjection, PublicUser, PublicUserProjection, UserPrivate } from "@spacebar/schemas"; @Entity({ - name: "users", + name: "users", }) export class User extends BaseClass { - @Column() - username: string; // username max length 32, min 2 (should be configurable) + @Column() + username: string; // username max length 32, min 2 (should be configurable) - @Column() - discriminator: string; // opaque string: 4 digits on discord.com + @Column() + discriminator: string; // opaque string: 4 digits on discord.com - @Column({ nullable: true }) - avatar?: string; // hash of the user avatar + @Column({ nullable: true }) + avatar?: string; // hash of the user avatar - @Column({ nullable: true }) - accent_color?: number; // banner color of user + @Column({ nullable: true }) + accent_color?: number; // banner color of user - @Column({ nullable: true }) - banner?: string; // hash of the user banner + @Column({ nullable: true }) + banner?: string; // hash of the user banner - // TODO: Separate `User` and `UserProfile` models - // puyo: changed from [number, number] because it breaks openapi - @Column({ nullable: true, type: "simple-array" }) - theme_colors?: number[]; + // TODO: Separate `User` and `UserProfile` models + // puyo: changed from [number, number] because it breaks openapi + @Column({ nullable: true, type: "simple-array" }) + theme_colors?: number[]; - @Column({ nullable: true }) - pronouns?: string; + @Column({ nullable: true }) + pronouns?: string; - @Column({ nullable: true, select: false }) - phone?: string; // phone number of the user + @Column({ nullable: true, select: false }) + phone?: string; // phone number of the user - @Column({ select: false }) - desktop: boolean = false; // if the user has desktop app installed + @Column({ select: false }) + desktop: boolean = false; // if the user has desktop app installed - @Column({ select: false }) - mobile: boolean = false; // if the user has mobile app installed + @Column({ select: false }) + mobile: boolean = false; // if the user has mobile app installed - @Column() - premium: boolean; // if user bought individual premium + @Column() + premium: boolean; // if user bought individual premium - @Column() - premium_type: number; // individual premium level + @Column() + premium_type: number; // individual premium level - @Column() - bot: boolean = false; // if user is bot + @Column() + bot: boolean = false; // if user is bot - @Column() - bio: string = ""; // short description of the user + @Column() + bio: string = ""; // short description of the user - @Column() - system: boolean = false; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author + @Column() + system: boolean = false; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author - @Column({ select: false }) - nsfw_allowed: boolean = true; // if the user can do age-restricted actions (NSFW channels/guilds/commands) // TODO: depending on age + @Column({ select: false }) + nsfw_allowed: boolean = true; // if the user can do age-restricted actions (NSFW channels/guilds/commands) // TODO: depending on age - @Column({ select: false }) - mfa_enabled: boolean = false; // if multi factor authentication is enabled + @Column({ select: false }) + mfa_enabled: boolean = false; // if multi factor authentication is enabled - @Column({ select: false, default: false }) - webauthn_enabled: boolean = false; // if webauthn multi factor authentication is enabled + @Column({ select: false, default: false }) + webauthn_enabled: boolean = false; // if webauthn multi factor authentication is enabled - @Column({ select: false, nullable: true }) - totp_secret?: string = ""; + @Column({ select: false, nullable: true }) + totp_secret?: string = ""; - @Column({ nullable: true, select: false }) - totp_last_ticket?: string = ""; + @Column({ nullable: true, select: false }) + totp_last_ticket?: string = ""; - @Column() - created_at: Date; // registration date + @Column() + created_at: Date; // registration date - @Column({ nullable: true }) - premium_since: Date; // premium date + @Column({ nullable: true }) + premium_since: Date; // premium date - @Column({ select: false }) - verified: boolean; // email is verified + @Column({ select: false }) + verified: boolean; // email is verified - @Column() - disabled: boolean = false; // if the account is disabled + @Column() + disabled: boolean = false; // if the account is disabled - @Column() - deleted: boolean = false; // if the user was deleted + @Column() + deleted: boolean = false; // if the user was deleted - @Column({ nullable: true, select: false }) - email?: string; // email of the user + @Column({ nullable: true, select: false }) + email?: string; // email of the user - @Column({ type: "bigint" }) - flags: number = 0; // UserFlags // TODO: generate + @Column({ type: "bigint" }) + flags: number = 0; // UserFlags // TODO: generate - @Column({ type: "bigint" }) - public_flags: number = 0; + @Column({ type: "bigint" }) + public_flags: number = 0; - @Column({ type: "bigint" }) - purchased_flags: number = 0; + @Column({ type: "bigint" }) + purchased_flags: number = 0; - @Column() - premium_usage_flags: number = 0; + @Column() + premium_usage_flags: number = 0; - @Column({ type: "bigint" }) - rights: string; + @Column({ type: "bigint" }) + rights: string; - @OneToMany(() => Session, (session: Session) => session.user) - sessions: Session[]; + @OneToMany(() => Session, (session: Session) => session.user) + sessions: Session[]; - @JoinColumn({ name: "relationship_ids" }) - @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { - cascade: true, - orphanedRowAction: "delete", - }) - relationships: Relationship[]; + @JoinColumn({ name: "relationship_ids" }) + @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { + cascade: true, + orphanedRowAction: "delete", + }) + relationships: Relationship[]; - @JoinColumn({ name: "connected_account_ids" }) - @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, { - cascade: true, - orphanedRowAction: "delete", - }) - connected_accounts: ConnectedAccount[]; + @JoinColumn({ name: "connected_account_ids" }) + @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, { + cascade: true, + orphanedRowAction: "delete", + }) + connected_accounts: ConnectedAccount[]; - @Column({ type: "simple-json", select: false }) - data: { - valid_tokens_since: Date; // all tokens with a previous issue date are invalid - hash?: string; // hash of the password, salt is saved in password (bcrypt) - }; + @Column({ type: "simple-json", select: false }) + data: { + valid_tokens_since: Date; // all tokens with a previous issue date are invalid + hash?: string; // hash of the password, salt is saved in password (bcrypt) + }; - @Column({ type: "simple-array", select: false }) - fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts + @Column({ type: "simple-array", select: false }) + fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts - @OneToOne(() => UserSettings, { - cascade: true, - orphanedRowAction: "delete", - nullable: true, - }) - @JoinColumn() - settings?: UserSettings; + @OneToOne(() => UserSettings, { + cascade: true, + orphanedRowAction: "delete", + nullable: true, + }) + @JoinColumn() + settings?: UserSettings; - // workaround to prevent fossord-unaware clients from deleting settings not used by them - @Column({ type: "simple-json", select: false }) - extended_settings: string = "{}"; + // workaround to prevent fossord-unaware clients from deleting settings not used by them + @Column({ type: "simple-json", select: false }) + extended_settings: string = "{}"; - @OneToMany(() => SecurityKey, (key: SecurityKey) => key.user) - security_keys: SecurityKey[]; + @OneToMany(() => SecurityKey, (key: SecurityKey) => key.user) + security_keys: SecurityKey[]; - @Column({ type: "simple-array", nullable: true }) - badge_ids?: string[]; + @Column({ type: "simple-array", nullable: true }) + badge_ids?: string[]; - // TODO: I don't like this method? - validate() { - if (this.discriminator) { - const discrim = Number(this.discriminator); - if (isNaN(discrim) || !(typeof discrim == "number") || !Number.isInteger(discrim) || discrim <= 0 || discrim >= 10000) - throw FieldErrors({ - discriminator: { - message: "Discriminator must be a number.", - code: "DISCRIMINATOR_INVALID", - }, - }); + // TODO: I don't like this method? + validate() { + if (this.discriminator) { + const discrim = Number(this.discriminator); + if (isNaN(discrim) || !(typeof discrim == "number") || !Number.isInteger(discrim) || discrim <= 0 || discrim >= 10000) + throw FieldErrors({ + discriminator: { + message: "Discriminator must be a number.", + code: "DISCRIMINATOR_INVALID", + }, + }); - this.discriminator = discrim.toString().padStart(4, "0"); - } - } + this.discriminator = discrim.toString().padStart(4, "0"); + } + } - toPublicUser() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const user: any = {}; - PublicUserProjection.forEach((x) => { - user[x] = this[x]; - }); - return user as PublicUser; - } + toPublicUser() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const user: any = {}; + PublicUserProjection.forEach((x) => { + user[x] = this[x]; + }); + return user as PublicUser; + } - toPrivateUser(extraFields: (keyof User)[] = []) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const user: any = {}; - [...PrivateUserProjection, ...extraFields].forEach((x) => { - user[x] = this[x]; - }); - return user as UserPrivate; - } + toPrivateUser(extraFields: (keyof User)[] = []) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const user: any = {}; + [...PrivateUserProjection, ...extraFields].forEach((x) => { + user[x] = this[x]; + }); + return user as UserPrivate; + } - static async getPublicUser(user_id: string, opts?: FindOneOptions) { - return await User.findOneOrFail({ - where: { id: user_id }, - ...opts, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix - }); - } + static async getPublicUser(user_id: string, opts?: FindOneOptions) { + return await User.findOneOrFail({ + where: { id: user_id }, + ...opts, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix + }); + } - public static async generateDiscriminator(username: string): Promise { - if (Config.get().register.incrementingDiscriminators) { - // discriminator will be incrementally generated + public static async generateDiscriminator(username: string): Promise { + if (Config.get().register.incrementingDiscriminators) { + // discriminator will be incrementally generated - // First we need to figure out the currently highest discrimnator for the given username and then increment it - const users = await User.find({ - where: { username }, - select: ["discriminator"], - }); - const highestDiscriminator = Math.max(0, ...users.map((u) => Number(u.discriminator))); + // First we need to figure out the currently highest discrimnator for the given username and then increment it + const users = await User.find({ + where: { username }, + select: ["discriminator"], + }); + const highestDiscriminator = Math.max(0, ...users.map((u) => Number(u.discriminator))); - const discriminator = highestDiscriminator + 1; - if (discriminator >= 10000) { - return undefined; - } + const discriminator = highestDiscriminator + 1; + if (discriminator >= 10000) { + return undefined; + } - return discriminator.toString().padStart(4, "0"); - } else { - // discriminator will be randomly generated + return discriminator.toString().padStart(4, "0"); + } else { + // discriminator will be randomly generated - // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists - // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database? - for (let tries = 0; tries < 5; tries++) { - const discriminator = Random.nextInt(1, 9999).toString().padStart(4, "0"); - const exists = await User.findOne({ - where: { discriminator, username: username }, - select: ["id"], - }); - if (!exists) return discriminator; - } + // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists + // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database? + for (let tries = 0; tries < 5; tries++) { + const discriminator = Random.nextInt(1, 9999).toString().padStart(4, "0"); + const exists = await User.findOne({ + where: { discriminator, username: username }, + select: ["id"], + }); + if (!exists) return discriminator; + } - return undefined; - } - } + return undefined; + } + } - static async register({ - email, - username, - password, - id, - req, - bot, - }: { - username: string; - password?: string; - email?: string; - date_of_birth?: Date; // "2000-04-03" - id?: string; - req?: Request; - bot?: boolean; - }) { - // trim special uf8 control characters -> Backspace, Newline, ... - username = trimSpecial(username); + static async register({ + email, + username, + password, + id, + req, + bot, + }: { + username: string; + password?: string; + email?: string; + date_of_birth?: Date; // "2000-04-03" + id?: string; + req?: Request; + bot?: boolean; + }) { + // trim special uf8 control characters -> Backspace, Newline, ... + username = trimSpecial(username); - const discriminator = await User.generateDiscriminator(username); - if (!discriminator) { - // We've failed to generate a valid and unused discriminator - throw FieldErrors({ - username: { - code: "USERNAME_TOO_MANY_USERS", - message: req?.t("auth:register.USERNAME_TOO_MANY_USERS") || "", - }, - }); - } + const discriminator = await User.generateDiscriminator(username); + if (!discriminator) { + // We've failed to generate a valid and unused discriminator + throw FieldErrors({ + username: { + code: "USERNAME_TOO_MANY_USERS", + message: req?.t("auth:register.USERNAME_TOO_MANY_USERS") || "", + }, + }); + } - // TODO: save date_of_birth - // apparently discord doesn't save the date of birth and just calculate if nsfw is allowed - // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - const language = req?.language === "en" ? "en-US" : req?.language || "en-US"; + // TODO: save date_of_birth + // apparently discord doesn't save the date of birth and just calculate if nsfw is allowed + // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false + const language = req?.language === "en" ? "en-US" : req?.language || "en-US"; - const settings = UserSettings.create({ - locale: language, - }); + const settings = UserSettings.create({ + locale: language, + }); - const user = User.create({ - username: username, - discriminator, - id: id || Snowflake.generate(), - email: email, - data: { - hash: password, - valid_tokens_since: new Date(), - }, - extended_settings: "{}", - settings: settings, + const user = User.create({ + username: username, + discriminator, + id: id || Snowflake.generate(), + email: email, + data: { + hash: password, + valid_tokens_since: new Date(), + }, + extended_settings: "{}", + settings: settings, - premium_since: Config.get().defaults.user.premium ? new Date() : undefined, - rights: Config.get().register.defaultRights, - premium: Config.get().defaults.user.premium ?? false, - premium_type: Config.get().defaults.user.premiumType ?? 0, - verified: Config.get().defaults.user.verified ?? true, - created_at: new Date(), - bot: !!bot, - }); + premium_since: Config.get().defaults.user.premium ? new Date() : undefined, + rights: Config.get().register.defaultRights, + premium: Config.get().defaults.user.premium ?? false, + premium_type: Config.get().defaults.user.premiumType ?? 0, + verified: Config.get().defaults.user.verified ?? true, + created_at: new Date(), + bot: !!bot, + }); - user.validate(); - await Promise.all([user.save(), settings.save()]); + user.validate(); + await Promise.all([user.save(), settings.save()]); - // send verification email if users aren't verified by default and we have an email - if (!Config.get().defaults.user.verified && email) { - await Email.sendVerifyEmail(user, email).catch((e) => { - console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`); - }); - } + // send verification email if users aren't verified by default and we have an email + if (!Config.get().defaults.user.verified && email) { + await Email.sendVerifyEmail(user, email).catch((e) => { + console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`); + }); + } - setImmediate(async () => { - if (bot) { - const { guild } = Config.get(); - if (!guild.autoJoin.bots) { - return; - } - } - if (Config.get().guild.autoJoin.enabled) { - for (const guild of Config.get().guild.autoJoin.guilds || []) { - await Member.addToGuild(user.id, guild).catch((e) => console.error("[Autojoin]", e)); - } - } - }); + setImmediate(async () => { + if (bot) { + const { guild } = Config.get(); + if (!guild.autoJoin.bots) { + return; + } + } + if (Config.get().guild.autoJoin.enabled) { + for (const guild of Config.get().guild.autoJoin.guilds || []) { + await Member.addToGuild(user.id, guild).catch((e) => console.error("[Autojoin]", e)); + } + } + }); - return user; - } + return user; + } - async getDmChannelWith(user_id: string) { - const qry = await Channel.getRepository() - .createQueryBuilder() - .leftJoinAndSelect("Channel.recipients", "rcp") - .where("Channel.type = :type", { type: ChannelType.DM }) - .andWhere("rcp.user_id IN (:...user_ids)", { user_ids: [this.id, user_id] }) - .groupBy("Channel.id") - .having("COUNT(rcp.user_id) = 2") - .getMany(); + async getDmChannelWith(user_id: string) { + const qry = await Channel.getRepository() + .createQueryBuilder() + .leftJoinAndSelect("Channel.recipients", "rcp") + .where("Channel.type = :type", { type: ChannelType.DM }) + .andWhere("rcp.user_id IN (:...user_ids)", { user_ids: [this.id, user_id] }) + .groupBy("Channel.id") + .having("COUNT(rcp.user_id) = 2") + .getMany(); - // Emma [it/its]@Rory&: is this technically a bug, or am I being too over-cautious? - if (qry.length > 1) { - console.warn(`[WARN] User(${this.id})#getDmChannel(${user_id}) returned multiple channels:`); - for (const channel of qry) { - console.warn(JSON.stringify(channel)); - } - throw new Error("Array contains more than one matching element"); - } + // Emma [it/its]@Rory&: is this technically a bug, or am I being too over-cautious? + if (qry.length > 1) { + console.warn(`[WARN] User(${this.id})#getDmChannel(${user_id}) returned multiple channels:`); + for (const channel of qry) { + console.warn(JSON.stringify(channel)); + } + throw new Error("Array contains more than one matching element"); + } - return qry[0]; - } + return qry[0]; + } - async getDmChannels() { - const qry = await Channel.getRepository() - .createQueryBuilder("channel") - .leftJoinAndSelect("channel.recipients", "rcp") - .where("channel.type = :type", { type: ChannelType.DM }) - .andWhere("rcp.user_id = :user_id", { user_id: this.id }) - .groupBy("channel.id") - .addGroupBy("rcp.id") - .having("COUNT(rcp.id) = 2") - .getMany(); + async getDmChannels() { + const qry = await Channel.getRepository() + .createQueryBuilder("channel") + .leftJoinAndSelect("channel.recipients", "rcp") + .where("channel.type = :type", { type: ChannelType.DM }) + .andWhere("rcp.user_id = :user_id", { user_id: this.id }) + .groupBy("channel.id") + .addGroupBy("rcp.id") + .having("COUNT(rcp.id) = 2") + .getMany(); - return qry; - } + return qry; + } } diff --git a/src/util/entities/UserSettings.ts b/src/util/entities/UserSettings.ts index eac09a9..f3f6cb0 100644 --- a/src/util/entities/UserSettings.ts +++ b/src/util/entities/UserSettings.ts @@ -21,122 +21,122 @@ import { CustomStatus, FriendSourceFlags, GuildFolder } from "@spacebar/schemas"; @Entity({ - name: "user_settings", + name: "user_settings", }) export class UserSettings extends BaseClassWithoutId { - @PrimaryGeneratedColumn() - index: string; + @PrimaryGeneratedColumn() + index: string; - @Column({ nullable: true }) - afk_timeout: number = 3600; + @Column({ nullable: true }) + afk_timeout: number = 3600; - @Column({ nullable: true }) - allow_accessibility_detection: boolean = true; + @Column({ nullable: true }) + allow_accessibility_detection: boolean = true; - @Column({ nullable: true }) - animate_emoji: boolean = true; + @Column({ nullable: true }) + animate_emoji: boolean = true; - @Column({ nullable: true }) - animate_stickers: number = 0; + @Column({ nullable: true }) + animate_stickers: number = 0; - @Column({ nullable: true }) - contact_sync_enabled: boolean = false; + @Column({ nullable: true }) + contact_sync_enabled: boolean = false; - @Column({ nullable: true }) - convert_emoticons: boolean = false; + @Column({ nullable: true }) + convert_emoticons: boolean = false; - @Column({ nullable: true, type: "simple-json" }) - custom_status: CustomStatus | null = null; + @Column({ nullable: true, type: "simple-json" }) + custom_status: CustomStatus | null = null; - @Column({ nullable: true }) - default_guilds_restricted: boolean = false; + @Column({ nullable: true }) + default_guilds_restricted: boolean = false; - @Column({ nullable: true }) - detect_platform_accounts: boolean = false; + @Column({ nullable: true }) + detect_platform_accounts: boolean = false; - @Column({ nullable: true }) - developer_mode: boolean = true; + @Column({ nullable: true }) + developer_mode: boolean = true; - @Column({ nullable: true }) - disable_games_tab: boolean = true; + @Column({ nullable: true }) + disable_games_tab: boolean = true; - @Column({ nullable: true }) - enable_tts_command: boolean = false; + @Column({ nullable: true }) + enable_tts_command: boolean = false; - @Column({ nullable: true }) - explicit_content_filter: number = 0; + @Column({ nullable: true }) + explicit_content_filter: number = 0; - @Column({ nullable: true }) - friend_discovery_flags: number = 0; + @Column({ nullable: true }) + friend_discovery_flags: number = 0; - @Column({ nullable: true, type: "simple-json" }) - friend_source_flags: FriendSourceFlags = { all: true }; + @Column({ nullable: true, type: "simple-json" }) + friend_source_flags: FriendSourceFlags = { all: true }; - @Column({ nullable: true }) - gateway_connected: boolean = false; + @Column({ nullable: true }) + gateway_connected: boolean = false; - @Column({ nullable: true }) - gif_auto_play: boolean = false; + @Column({ nullable: true }) + gif_auto_play: boolean = false; - @Column({ nullable: true, type: "simple-json" }) - guild_folders: GuildFolder[] = []; // every top guild is displayed as a "folder" + @Column({ nullable: true, type: "simple-json" }) + guild_folders: GuildFolder[] = []; // every top guild is displayed as a "folder" - @Column({ nullable: true, type: "simple-json" }) - guild_positions: string[] = []; // guild ids ordered by position + @Column({ nullable: true, type: "simple-json" }) + guild_positions: string[] = []; // guild ids ordered by position - @Column({ nullable: true }) - inline_attachment_media: boolean = true; + @Column({ nullable: true }) + inline_attachment_media: boolean = true; - @Column({ nullable: true }) - inline_embed_media: boolean = true; + @Column({ nullable: true }) + inline_embed_media: boolean = true; - @Column({ nullable: true }) - locale: string = "en-US"; // en_US + @Column({ nullable: true }) + locale: string = "en-US"; // en_US - @Column({ nullable: true }) - message_display_compact: boolean = false; + @Column({ nullable: true }) + message_display_compact: boolean = false; - @Column({ nullable: true }) - native_phone_integration_enabled: boolean = true; + @Column({ nullable: true }) + native_phone_integration_enabled: boolean = true; - @Column({ nullable: true }) - render_embeds: boolean = true; + @Column({ nullable: true }) + render_embeds: boolean = true; - @Column({ nullable: true }) - render_reactions: boolean = true; + @Column({ nullable: true }) + render_reactions: boolean = true; - @Column({ nullable: true, type: "simple-json" }) - restricted_guilds: string[] = []; + @Column({ nullable: true, type: "simple-json" }) + restricted_guilds: string[] = []; - @Column({ nullable: true }) - show_current_game: boolean = true; + @Column({ nullable: true }) + show_current_game: boolean = true; - @Column({ nullable: true }) - status: "online" | "offline" | "dnd" | "idle" | "invisible" = "online"; + @Column({ nullable: true }) + status: "online" | "offline" | "dnd" | "idle" | "invisible" = "online"; - @Column({ nullable: true }) - stream_notifications_enabled: boolean = false; + @Column({ nullable: true }) + stream_notifications_enabled: boolean = false; - @Column({ nullable: true }) - theme: "dark" | "light" = "dark"; // dark + @Column({ nullable: true }) + theme: "dark" | "light" = "dark"; // dark - @Column({ nullable: true }) - timezone_offset: number = 0; // e.g -60 + @Column({ nullable: true }) + timezone_offset: number = 0; // e.g -60 - @Column({ nullable: true }) - view_nsfw_guilds: boolean = true; + @Column({ nullable: true }) + view_nsfw_guilds: boolean = true; - public static async getOrDefault(userId: string) { - // raw sql query - const userSettingsIndex = (await this.getRepository().query('SELECT "settingsIndex" FROM users WHERE id = $1', [userId]))[0]?.settingsIndex as string | null; + public static async getOrDefault(userId: string) { + // raw sql query + const userSettingsIndex = (await this.getRepository().query('SELECT "settingsIndex" FROM users WHERE id = $1', [userId]))[0]?.settingsIndex as string | null; - console.log(`[INFO/UserSettings] Fetched settings index for user ${userId}:`, userSettingsIndex); + console.log(`[INFO/UserSettings] Fetched settings index for user ${userId}:`, userSettingsIndex); - if (!userSettingsIndex) return new UserSettings(); + if (!userSettingsIndex) return new UserSettings(); - const settings = await UserSettings.findOne({ where: { index: userSettingsIndex } }); - if (!settings) return new UserSettings(); + const settings = await UserSettings.findOne({ where: { index: userSettingsIndex } }); + if (!settings) return new UserSettings(); - return settings; - } + return settings; + } } diff --git a/src/util/entities/UserSettingsProtos.ts b/src/util/entities/UserSettingsProtos.ts index 2171738..c08b98f 100644 --- a/src/util/entities/UserSettingsProtos.ts +++ b/src/util/entities/UserSettingsProtos.ts @@ -22,124 +22,124 @@ import { FrecencyUserSettings, PreloadedUserSettings } from "discord-protos"; @Entity({ - name: "user_settings_protos", + name: "user_settings_protos", }) export class UserSettingsProtos extends BaseClassWithoutId { - @OneToOne(() => User, { - cascade: true, - orphanedRowAction: "delete", - eager: false, - }) - @JoinColumn({ name: "user_id" }) - user: User; + @OneToOne(() => User, { + cascade: true, + orphanedRowAction: "delete", + eager: false, + }) + @JoinColumn({ name: "user_id" }) + user: User; - @PrimaryIdColumn({ type: "text" }) - user_id: string; + @PrimaryIdColumn({ type: "text" }) + user_id: string; - @Column({ nullable: true, type: String, name: "userSettings" }) - _userSettings: string | undefined; + @Column({ nullable: true, type: String, name: "userSettings" }) + _userSettings: string | undefined; - @Column({ nullable: true, type: String, name: "frecencySettings" }) - _frecencySettings: string | undefined; + @Column({ nullable: true, type: String, name: "frecencySettings" }) + _frecencySettings: string | undefined; - // @Column({nullable: true, type: "simple-json"}) - // testSettings: {}; + // @Column({nullable: true, type: "simple-json"}) + // testSettings: {}; - bigintReplacer(_key: string, value: unknown): unknown { - if (typeof value === "bigint") { - return (value as bigint).toString(); - } else if (value instanceof Uint8Array) { - return { - __type: "Uint8Array", - data: Array.from(value as Uint8Array) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""), - }; - } else { - return value; - } - } + bigintReplacer(_key: string, value: unknown): unknown { + if (typeof value === "bigint") { + return (value as bigint).toString(); + } else if (value instanceof Uint8Array) { + return { + __type: "Uint8Array", + data: Array.from(value as Uint8Array) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""), + }; + } else { + return value; + } + } - bigintReviver(_key: string, value: unknown): unknown { - if (typeof value === "string" && /^\d+n$/.test(value)) { - return BigInt((value as string).slice(0, -1)); - } else if (typeof value === "object" && value !== null && "__type" in value) { - if (value.__type === "Uint8Array" && "data" in value) { - return new Uint8Array((value.data as string).match(/.{1,2}/g)!.map((byte: string) => parseInt(byte, 16))); - } - } - return value; - } + bigintReviver(_key: string, value: unknown): unknown { + if (typeof value === "string" && /^\d+n$/.test(value)) { + return BigInt((value as string).slice(0, -1)); + } else if (typeof value === "object" && value !== null && "__type" in value) { + if (value.__type === "Uint8Array" && "data" in value) { + return new Uint8Array((value.data as string).match(/.{1,2}/g)!.map((byte: string) => parseInt(byte, 16))); + } + } + return value; + } - get userSettings(): PreloadedUserSettings | undefined { - if (!this._userSettings) return undefined; - return PreloadedUserSettings.fromJson(JSON.parse(this._userSettings, this.bigintReviver)); - } + get userSettings(): PreloadedUserSettings | undefined { + if (!this._userSettings) return undefined; + return PreloadedUserSettings.fromJson(JSON.parse(this._userSettings, this.bigintReviver)); + } - set userSettings(value: PreloadedUserSettings | undefined) { - if (value) { - // this._userSettings = JSON.stringify(value, this.bigintReplacer); - this._userSettings = PreloadedUserSettings.toJsonString(value); - } else { - this._userSettings = undefined; - } - } + set userSettings(value: PreloadedUserSettings | undefined) { + if (value) { + // this._userSettings = JSON.stringify(value, this.bigintReplacer); + this._userSettings = PreloadedUserSettings.toJsonString(value); + } else { + this._userSettings = undefined; + } + } - get frecencySettings(): FrecencyUserSettings | undefined { - if (!this._frecencySettings) return undefined; - return FrecencyUserSettings.fromJson(JSON.parse(this._frecencySettings, this.bigintReviver)); - } + get frecencySettings(): FrecencyUserSettings | undefined { + if (!this._frecencySettings) return undefined; + return FrecencyUserSettings.fromJson(JSON.parse(this._frecencySettings, this.bigintReviver)); + } - set frecencySettings(value: FrecencyUserSettings | undefined) { - if (value) { - this._frecencySettings = JSON.stringify(value, this.bigintReplacer); - } else { - this._frecencySettings = undefined; - } - } + set frecencySettings(value: FrecencyUserSettings | undefined) { + if (value) { + this._frecencySettings = JSON.stringify(value, this.bigintReplacer); + } else { + this._frecencySettings = undefined; + } + } - static async getOrDefault(user_id: string, save: boolean = false): Promise { - const user = await User.findOneOrFail({ - where: { id: user_id }, - select: { settings: true }, - }); + static async getOrDefault(user_id: string, save: boolean = false): Promise { + const user = await User.findOneOrFail({ + where: { id: user_id }, + select: { settings: true }, + }); - let userSettings = await UserSettingsProtos.findOne({ - where: { user_id }, - }); + let userSettings = await UserSettingsProtos.findOne({ + where: { user_id }, + }); - let modified = false; - if (!userSettings) { - userSettings = UserSettingsProtos.create({ - user_id, - }); - modified = true; - } + let modified = false; + if (!userSettings) { + userSettings = UserSettingsProtos.create({ + user_id, + }); + modified = true; + } - if (!userSettings.userSettings) { - userSettings.userSettings = PreloadedUserSettings.create({ - versions: { - dataVersion: 0, - clientVersion: 0, - serverVersion: 0, - }, - }); - modified = true; - } + if (!userSettings.userSettings) { + userSettings.userSettings = PreloadedUserSettings.create({ + versions: { + dataVersion: 0, + clientVersion: 0, + serverVersion: 0, + }, + }); + modified = true; + } - if (!userSettings.frecencySettings) { - userSettings.frecencySettings = FrecencyUserSettings.create({ - versions: { - dataVersion: 0, - clientVersion: 0, - serverVersion: 0, - }, - }); - modified = true; - } + if (!userSettings.frecencySettings) { + userSettings.frecencySettings = FrecencyUserSettings.create({ + versions: { + dataVersion: 0, + clientVersion: 0, + serverVersion: 0, + }, + }); + modified = true; + } - if (modified && save) userSettings = await userSettings.save(); + if (modified && save) userSettings = await userSettings.save(); - return userSettings; - } + return userSettings; + } } diff --git a/src/util/entities/ValidRegistrationTokens.ts b/src/util/entities/ValidRegistrationTokens.ts index 20e2c76..f8cb35a 100644 --- a/src/util/entities/ValidRegistrationTokens.ts +++ b/src/util/entities/ValidRegistrationTokens.ts @@ -19,15 +19,15 @@ import { BaseEntity, Column, Entity, PrimaryColumn } from "typeorm"; @Entity({ - name: "valid_registration_tokens", + name: "valid_registration_tokens", }) export class ValidRegistrationToken extends BaseEntity { - @PrimaryColumn() - token: string; + @PrimaryColumn() + token: string; - @Column() - created_at: Date = new Date(); + @Column() + created_at: Date = new Date(); - @Column() - expires_at: Date; + @Column() + expires_at: Date; } diff --git a/src/util/entities/VoiceState.ts b/src/util/entities/VoiceState.ts index 85fe27f..5abdb7d 100644 --- a/src/util/entities/VoiceState.ts +++ b/src/util/entities/VoiceState.ts @@ -26,82 +26,82 @@ //https://gist.github.com/vassjozsef/e482c65df6ee1facaace8b3c9ff66145#file-voice_state-ex @Entity({ - name: "voice_states", + name: "voice_states", }) export class VoiceState extends BaseClass { - @Column({ nullable: true }) - @RelationId((voice_state: VoiceState) => voice_state.guild) - guild_id: string; + @Column({ nullable: true }) + @RelationId((voice_state: VoiceState) => voice_state.guild) + guild_id: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild) => guild.voice_states, { - onDelete: "CASCADE", - }) - guild?: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, (guild) => guild.voice_states, { + onDelete: "CASCADE", + }) + guild?: Guild; - @Column({ nullable: true }) - @RelationId((voice_state: VoiceState) => voice_state.channel) - channel_id: string; + @Column({ nullable: true }) + @RelationId((voice_state: VoiceState) => voice_state.channel) + channel_id: string; - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - channel: Channel; + @JoinColumn({ name: "channel_id" }) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) + channel: Channel; - @Column({ nullable: true }) - @RelationId((voice_state: VoiceState) => voice_state.user) - user_id: string; + @Column({ nullable: true }) + @RelationId((voice_state: VoiceState) => voice_state.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - // @JoinColumn([{ name: "user_id", referencedColumnName: "id" },{ name: "guild_id", referencedColumnName: "guild_id" }]) - // @ManyToOne(() => Member, { - // onDelete: "CASCADE", - // }) - //TODO find a way to make it work without breaking Guild.voice_states - member: Member; + // @JoinColumn([{ name: "user_id", referencedColumnName: "id" },{ name: "guild_id", referencedColumnName: "guild_id" }]) + // @ManyToOne(() => Member, { + // onDelete: "CASCADE", + // }) + //TODO find a way to make it work without breaking Guild.voice_states + member: Member; - @Column() - session_id: string; + @Column() + session_id: string; - @Column({ nullable: true }) - token: string; + @Column({ nullable: true }) + token: string; - @Column() - deaf: boolean; + @Column() + deaf: boolean; - @Column() - mute: boolean; + @Column() + mute: boolean; - @Column() - self_deaf: boolean; + @Column() + self_deaf: boolean; - @Column() - self_mute: boolean; + @Column() + self_mute: boolean; - @Column({ nullable: true }) - self_stream?: boolean; + @Column({ nullable: true }) + self_stream?: boolean; - @Column() - self_video: boolean; + @Column() + self_video: boolean; - @Column() - suppress: boolean; // whether this user is muted by the current user + @Column() + suppress: boolean; // whether this user is muted by the current user - @Column({ nullable: true, default: null }) - request_to_speak_timestamp?: Date; + @Column({ nullable: true, default: null }) + request_to_speak_timestamp?: Date; - toPublicVoiceState() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const voiceState: any = {}; - PublicVoiceStateProjection.forEach((x) => { - voiceState[x] = this[x]; - }); - return voiceState as PublicVoiceState; - } + toPublicVoiceState() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const voiceState: any = {}; + PublicVoiceStateProjection.forEach((x) => { + voiceState[x] = this[x]; + }); + return voiceState as PublicVoiceState; + } } diff --git a/src/util/entities/Webhook.ts b/src/util/entities/Webhook.ts index 6a12416..f631c39 100644 --- a/src/util/entities/Webhook.ts +++ b/src/util/entities/Webhook.ts @@ -25,80 +25,80 @@ import { WebhookType } from "@spacebar/schemas"; @Entity({ - name: "webhooks", + name: "webhooks", }) export class Webhook extends BaseClass { - @Column({ type: "int" }) - type: WebhookType; + @Column({ type: "int" }) + type: WebhookType; - @Column({ nullable: true }) - name: string; + @Column({ nullable: true }) + name: string; - @Column({ nullable: true }) - avatar: string; + @Column({ nullable: true }) + avatar: string; - @Column({ nullable: true }) - token?: string; + @Column({ nullable: true }) + token?: string; - @Column({ nullable: true }) - @RelationId((webhook: Webhook) => webhook.guild) - guild_id?: string; + @Column({ nullable: true }) + @RelationId((webhook: Webhook) => webhook.guild) + guild_id?: string; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { - onDelete: "CASCADE", - }) - guild?: Guild; + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) + guild?: Guild; - @Column({ nullable: true }) - @RelationId((webhook: Webhook) => webhook.channel) - channel_id: string; + @Column({ nullable: true }) + @RelationId((webhook: Webhook) => webhook.channel) + channel_id: string; - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - channel: Channel; + @JoinColumn({ name: "channel_id" }) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) + channel: Channel; - @Column({ nullable: true }) - @RelationId((webhook: Webhook) => webhook.application) - application_id: string; + @Column({ nullable: true }) + @RelationId((webhook: Webhook) => webhook.application) + application_id: string; - @JoinColumn({ name: "application_id" }) - @ManyToOne(() => Application, { - onDelete: "CASCADE", - }) - application: Application; + @JoinColumn({ name: "application_id" }) + @ManyToOne(() => Application, { + onDelete: "CASCADE", + }) + application: Application; - @Column({ nullable: true }) - @RelationId((webhook: Webhook) => webhook.user) - user_id: string; + @Column({ nullable: true }) + @RelationId((webhook: Webhook) => webhook.user) + user_id: string; - @JoinColumn({ name: "user_id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - user: User; + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { + onDelete: "CASCADE", + }) + user: User; - @Column({ nullable: true }) - @RelationId((webhook: Webhook) => webhook.guild) - source_guild_id?: string; + @Column({ nullable: true }) + @RelationId((webhook: Webhook) => webhook.guild) + source_guild_id?: string; - @JoinColumn({ name: "source_guild_id" }) - @ManyToOne(() => Guild, { - onDelete: "CASCADE", - }) - source_guild?: Guild; + @JoinColumn({ name: "source_guild_id" }) + @ManyToOne(() => Guild, { + onDelete: "CASCADE", + }) + source_guild?: Guild; - @Column({ nullable: true }) - @RelationId((webhook: Webhook) => webhook.channel) - source_channel_id: string; + @Column({ nullable: true }) + @RelationId((webhook: Webhook) => webhook.channel) + source_channel_id: string; - @JoinColumn({ name: "source_channel_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - source_channel: Channel; + @JoinColumn({ name: "source_channel_id" }) + @ManyToOne(() => Channel, { + onDelete: "CASCADE", + }) + source_channel: Channel; - url: string; + url: string; } diff --git a/src/util/imports/Erlpack.ts b/src/util/imports/Erlpack.ts index a69cffb..46431f0 100644 --- a/src/util/imports/Erlpack.ts +++ b/src/util/imports/Erlpack.ts @@ -7,6 +7,6 @@ // @fc-license-skip export type ErlpackType = { - pack: (data: any) => Buffer; - unpack: (data: Buffer) => T; + pack: (data: any) => Buffer; + unpack: (data: Buffer) => T; }; diff --git a/src/util/imports/Interactions.ts b/src/util/imports/Interactions.ts index 49ec493..fbb74e5 100644 --- a/src/util/imports/Interactions.ts +++ b/src/util/imports/Interactions.ts @@ -20,15 +20,15 @@ import { InteractionType, Snowflake } from "@spacebar/util"; interface PendingInteraction { - timeout: NodeJS.Timeout; - applicationId: string; - userId: string; - channelId?: string; - guildId?: string; - nonce?: string; - type: InteractionType; - commandType: ApplicationCommandType; - commandName: string; + timeout: NodeJS.Timeout; + applicationId: string; + userId: string; + channelId?: string; + guildId?: string; + nonce?: string; + type: InteractionType; + commandType: ApplicationCommandType; + commandName: string; } export const pendingInteractions = new Map(); diff --git a/src/util/imports/Jimp.ts b/src/util/imports/Jimp.ts index c1389e0..a5257e4 100644 --- a/src/util/imports/Jimp.ts +++ b/src/util/imports/Jimp.ts @@ -19,5 +19,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ export type JimpType = { - read: (data: Buffer) => Promise; + read: (data: Buffer) => Promise; }; diff --git a/src/util/imports/OrmUtils.ts b/src/util/imports/OrmUtils.ts index 3a11be2..5a3c99e 100644 --- a/src/util/imports/OrmUtils.ts +++ b/src/util/imports/OrmUtils.ts @@ -3,97 +3,97 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // @fc-license-skip export class OrmUtils { - // Checks if it's an object made by Object.create(null), {} or new Object() - private static isPlainObject(item: unknown) { - if (item === null || item === undefined) { - return false; - } + // Checks if it's an object made by Object.create(null), {} or new Object() + private static isPlainObject(item: unknown) { + if (item === null || item === undefined) { + return false; + } - return !item.constructor || item.constructor === Object; - } + return !item.constructor || item.constructor === Object; + } - private static mergeArrayKey(target: any, key: number, value: any, memo: Map) { - // Have we seen this before? Prevent infinite recursion. - if (memo.has(value)) { - target[key] = memo.get(value); - return; - } + private static mergeArrayKey(target: any, key: number, value: any, memo: Map) { + // Have we seen this before? Prevent infinite recursion. + if (memo.has(value)) { + target[key] = memo.get(value); + return; + } - if (value instanceof Promise) { - // Skip promises entirely. - // This is a hold-over from the old code & is because we don't want to pull in - // the lazy fields. Ideally we'd remove these promises via another function first - // but for now we have to do it here. - return; - } + if (value instanceof Promise) { + // Skip promises entirely. + // This is a hold-over from the old code & is because we don't want to pull in + // the lazy fields. Ideally we'd remove these promises via another function first + // but for now we have to do it here. + return; + } - if (!this.isPlainObject(value) && !Array.isArray(value)) { - target[key] = value; - return; - } + if (!this.isPlainObject(value) && !Array.isArray(value)) { + target[key] = value; + return; + } - if (!target[key]) { - target[key] = Array.isArray(value) ? [] : {}; - } + if (!target[key]) { + target[key] = Array.isArray(value) ? [] : {}; + } - memo.set(value, target[key]); - this.merge(target[key], value, memo); - memo.delete(value); - } + memo.set(value, target[key]); + this.merge(target[key], value, memo); + memo.delete(value); + } - private static mergeObjectKey(target: any, key: string, value: any, memo: Map) { - // Have we seen this before? Prevent infinite recursion. - if (memo.has(value)) { - Object.assign(target, { [key]: memo.get(value) }); - return; - } + private static mergeObjectKey(target: any, key: string, value: any, memo: Map) { + // Have we seen this before? Prevent infinite recursion. + if (memo.has(value)) { + Object.assign(target, { [key]: memo.get(value) }); + return; + } - if (value instanceof Promise) { - // Skip promises entirely. - // This is a hold-over from the old code & is because we don't want to pull in - // the lazy fields. Ideally we'd remove these promises via another function first - // but for now we have to do it here. - return; - } + if (value instanceof Promise) { + // Skip promises entirely. + // This is a hold-over from the old code & is because we don't want to pull in + // the lazy fields. Ideally we'd remove these promises via another function first + // but for now we have to do it here. + return; + } - if (!this.isPlainObject(value) && !Array.isArray(value)) { - Object.assign(target, { [key]: value }); - return; - } + if (!this.isPlainObject(value) && !Array.isArray(value)) { + Object.assign(target, { [key]: value }); + return; + } - if (!target[key]) { - Object.assign(target, { [key]: value }); - } + if (!target[key]) { + Object.assign(target, { [key]: value }); + } - memo.set(value, target[key]); - this.merge(target[key], value, memo); - memo.delete(value); - } + memo.set(value, target[key]); + this.merge(target[key], value, memo); + memo.delete(value); + } - private static merge(target: any, source: any, memo: Map = new Map()): any { - if (Array.isArray(target) && Array.isArray(source)) { - for (let key = 0; key < source.length; key++) { - this.mergeArrayKey(target, key, source[key], memo); - } - } else { - for (const key of Object.keys(source)) { - this.mergeObjectKey(target, key, source[key], memo); - } - } - } + private static merge(target: any, source: any, memo: Map = new Map()): any { + if (Array.isArray(target) && Array.isArray(source)) { + for (let key = 0; key < source.length; key++) { + this.mergeArrayKey(target, key, source[key], memo); + } + } else { + for (const key of Object.keys(source)) { + this.mergeObjectKey(target, key, source[key], memo); + } + } + } - /** - * Deep Object.assign. - */ - static mergeDeep(target: any, ...sources: any[]): any { - if (!sources.length) { - return target; - } + /** + * Deep Object.assign. + */ + static mergeDeep(target: any, ...sources: any[]): any { + if (!sources.length) { + return target; + } - for (const source of sources) { - OrmUtils.merge(target, source); - } + for (const source of sources) { + OrmUtils.merge(target, source); + } - return target; - } + return target; + } } diff --git a/src/util/interfaces/Activity.ts b/src/util/interfaces/Activity.ts index 0227f24..0c9c8e2 100644 --- a/src/util/interfaces/Activity.ts +++ b/src/util/interfaces/Activity.ts @@ -17,57 +17,57 @@ */ export interface Activity { - name: string; // the activity's name - type: ActivityType; // activity type // TODO: check if its between range 0-5 - url?: string; // stream url, is validated when type is 1 - created_at?: number; // unix timestamp of when the activity was added to the user's session - timestamps?: { - // unix timestamps for start and/or end of the game - start: number; - end: number; - }; - application_id?: string; // application id for the game - details?: string; - state?: string; - emoji?: { - name: string; - id?: string; - animated: boolean; - }; - party?: { - id?: string; - size?: number[]; // used to show the party's current and maximum size // TODO: array length 2 - }; - assets?: { - large_image?: string; // the id for a large asset of the activity, usually a snowflake - large_text?: string; // text displayed when hovering over the large image of the activity - small_image?: string; // the id for a small asset of the activity, usually a snowflake - small_text?: string; // text displayed when hovering over the small image of the activity - }; - secrets?: { - join?: string; // the secret for joining a party - spectate?: string; // the secret for spectating a game - match?: string; // the secret for a specific instanced match - }; - instance?: boolean; - flags: string; // activity flags OR d together, describes what the payload includes + name: string; // the activity's name + type: ActivityType; // activity type // TODO: check if its between range 0-5 + url?: string; // stream url, is validated when type is 1 + created_at?: number; // unix timestamp of when the activity was added to the user's session + timestamps?: { + // unix timestamps for start and/or end of the game + start: number; + end: number; + }; + application_id?: string; // application id for the game + details?: string; + state?: string; + emoji?: { + name: string; + id?: string; + animated: boolean; + }; + party?: { + id?: string; + size?: number[]; // used to show the party's current and maximum size // TODO: array length 2 + }; + assets?: { + large_image?: string; // the id for a large asset of the activity, usually a snowflake + large_text?: string; // text displayed when hovering over the large image of the activity + small_image?: string; // the id for a small asset of the activity, usually a snowflake + small_text?: string; // text displayed when hovering over the small image of the activity + }; + secrets?: { + join?: string; // the secret for joining a party + spectate?: string; // the secret for spectating a game + match?: string; // the secret for a specific instanced match + }; + instance?: boolean; + flags: string; // activity flags OR d together, describes what the payload includes - // Spotify and other rich presence data - id?: string; - sync_id?: string; - metadata?: { - // spotify - context_uri?: string; - album_id: string; - artist_ids: string[]; - }; - session_id: string; + // Spotify and other rich presence data + id?: string; + sync_id?: string; + metadata?: { + // spotify + context_uri?: string; + album_id: string; + artist_ids: string[]; + }; + session_id: string; } export enum ActivityType { - GAME = 0, - STREAMING = 1, - LISTENING = 2, - CUSTOM = 4, - COMPETING = 5, + GAME = 0, + STREAMING = 1, + LISTENING = 2, + CUSTOM = 4, + COMPETING = 5, } diff --git a/src/util/interfaces/ConnectedAccount.ts b/src/util/interfaces/ConnectedAccount.ts index 7278c0c..57cda25 100644 --- a/src/util/interfaces/ConnectedAccount.ts +++ b/src/util/interfaces/ConnectedAccount.ts @@ -17,19 +17,19 @@ */ export interface ConnectedAccountCommonOAuthTokenResponse { - access_token: string; - token_type: string; - scope: string; - refresh_token?: string; - expires_in?: number; + access_token: string; + token_type: string; + scope: string; + refresh_token?: string; + expires_in?: number; } export interface ConnectedAccountTokenData { - access_token: string; - token_type?: string; - scope?: string; - refresh_token?: string; - expires_in?: number; - expires_at?: number; - fetched_at: number; + access_token: string; + token_type?: string; + scope?: string; + refresh_token?: string; + expires_in?: number; + expires_at?: number; + fetched_at: number; } diff --git a/src/util/interfaces/Event.ts b/src/util/interfaces/Event.ts index 1f8df68..0fd4c62 100644 --- a/src/util/interfaces/Event.ts +++ b/src/util/interfaces/Event.ts @@ -17,123 +17,123 @@ */ import { - ConnectedAccount, - Interaction, - Message, - Invite, - Role, - Emoji, - Channel, - User, - Sticker, - Activity, - Status, - Presence, - UserSettings, - IReadyGuildDTO, - ReadState, - ReadyUserGuildSettingsEntries, - ReadyPrivateChannel, - GuildOrUnavailable, - Snowflake, + ConnectedAccount, + Interaction, + Message, + Invite, + Role, + Emoji, + Channel, + User, + Sticker, + Activity, + Status, + Presence, + UserSettings, + IReadyGuildDTO, + ReadState, + ReadyUserGuildSettingsEntries, + ReadyPrivateChannel, + GuildOrUnavailable, + Snowflake, } from "@spacebar/util"; import { JsonValue } from "@protobuf-ts/runtime"; import { ApplicationCommand, GuildCreateResponse, PartialEmoji, PublicMember, PublicUser, PublicVoiceState, RelationshipType, UserPrivate } from "@spacebar/schemas"; export interface Event { - guild_id?: string; - user_id?: string; - channel_id?: string; - created_at?: Date; - event: EVENT; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data?: any; - origin?: string; + guild_id?: string; + user_id?: string; + channel_id?: string; + created_at?: Date; + event: EVENT; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data?: any; + origin?: string; } // ! Custom Events that shouldn't get sent to the client but processed by the server export interface InvalidatedEvent extends Event { - event: "INVALIDATED"; + event: "INVALIDATED"; } export interface PublicRelationship { - id: string; - user: PublicUser; - type: RelationshipType; + id: string; + user: PublicUser; + type: RelationshipType; } // ! END Custom Events that shouldn't get sent to the client but processed by the server export interface ReadyEventData { - v: number; - user: UserPrivate; - private_channels: ReadyPrivateChannel[]; // this will be empty for bots - presences: Presence[]; - session_id: string; // resuming - guilds: IReadyGuildDTO[] | GuildOrUnavailable[]; // depends on capability - analytics_token?: string; - connected_accounts?: ConnectedAccount[]; - consents?: { - personalization?: { - consented?: boolean; - }; - }; - country_code?: string; // e.g. DE - friend_suggestion_count?: number; - geo_ordered_rtc_regions?: string[]; // ["europe","russie","india","us-east","us-central"] - experiments?: [number, number, number, number, number][]; - guild_experiments?: [ - // ? what are guild_experiments? - // this is the structure of it: - number, - null, - number, - [[number, { e: number; s: number }[]]], - [number, [[number, [number, number]]]], - { b: number; k: bigint[] }[], - ][]; - guild_join_requests?: unknown[]; // ? what is this? this is new - shard?: [number, number]; - user_settings?: UserSettings; - user_settings_proto?: string; - user_settings_proto_json?: JsonValue; - relationships?: PublicRelationship[]; // TODO - read_state: { - entries: ReadState[]; // TODO - partial: boolean; - version: number; - }; - user_guild_settings?: { - entries: ReadyUserGuildSettingsEntries[]; - version: number; - partial: boolean; - }; - application?: { - id: string; - flags: number; - }; - merged_members?: PublicMember[][]; - // probably all users who the user is in contact with - users?: PublicUser[]; - sessions: unknown[]; - api_code_version: number; - tutorial: number | null; - resume_gateway_url: string; - session_type: string; - auth_session_id_hash: string; - required_action?: - | "REQUIRE_VERIFIED_EMAIL" - | "REQUIRE_VERIFIED_PHONE" - | "REQUIRE_CAPTCHA" // TODO: allow these to be triggered - | "TOS_UPDATE_ACKNOWLEDGMENT" - | "AGREEMENTS"; - notification_settings: { - flags: number; - }; - game_relationships: never[]; // what is this? - auth_token?: string; // if enabled in capabilities - _trace?: string[]; // trace of the request, used for debugging + v: number; + user: UserPrivate; + private_channels: ReadyPrivateChannel[]; // this will be empty for bots + presences: Presence[]; + session_id: string; // resuming + guilds: IReadyGuildDTO[] | GuildOrUnavailable[]; // depends on capability + analytics_token?: string; + connected_accounts?: ConnectedAccount[]; + consents?: { + personalization?: { + consented?: boolean; + }; + }; + country_code?: string; // e.g. DE + friend_suggestion_count?: number; + geo_ordered_rtc_regions?: string[]; // ["europe","russie","india","us-east","us-central"] + experiments?: [number, number, number, number, number][]; + guild_experiments?: [ + // ? what are guild_experiments? + // this is the structure of it: + number, + null, + number, + [[number, { e: number; s: number }[]]], + [number, [[number, [number, number]]]], + { b: number; k: bigint[] }[], + ][]; + guild_join_requests?: unknown[]; // ? what is this? this is new + shard?: [number, number]; + user_settings?: UserSettings; + user_settings_proto?: string; + user_settings_proto_json?: JsonValue; + relationships?: PublicRelationship[]; // TODO + read_state: { + entries: ReadState[]; // TODO + partial: boolean; + version: number; + }; + user_guild_settings?: { + entries: ReadyUserGuildSettingsEntries[]; + version: number; + partial: boolean; + }; + application?: { + id: string; + flags: number; + }; + merged_members?: PublicMember[][]; + // probably all users who the user is in contact with + users?: PublicUser[]; + sessions: unknown[]; + api_code_version: number; + tutorial: number | null; + resume_gateway_url: string; + session_type: string; + auth_session_id_hash: string; + required_action?: + | "REQUIRE_VERIFIED_EMAIL" + | "REQUIRE_VERIFIED_PHONE" + | "REQUIRE_CAPTCHA" // TODO: allow these to be triggered + | "TOS_UPDATE_ACKNOWLEDGMENT" + | "AGREEMENTS"; + notification_settings: { + flags: number; + }; + game_relationships: never[]; // what is this? + auth_token?: string; // if enabled in capabilities + _trace?: string[]; // trace of the request, used for debugging } export type TraceNode = { micros: number; calls: TraceNode[] } | { micros: number } | string; @@ -141,631 +141,631 @@ export type TraceRoot = [string, { micros: number; calls: TraceNode[] }]; export interface ReadyEvent extends Event { - event: "READY"; - data: ReadyEventData; + event: "READY"; + data: ReadyEventData; } export interface ChannelCreateEvent extends Event { - event: "CHANNEL_CREATE"; - data: Channel; + event: "CHANNEL_CREATE"; + data: Channel; } export interface ChannelUpdateEvent extends Event { - event: "CHANNEL_UPDATE"; - data: Channel; + event: "CHANNEL_UPDATE"; + data: Channel; } export interface ChannelDeleteEvent extends Event { - event: "CHANNEL_DELETE"; - data: Channel; + event: "CHANNEL_DELETE"; + data: Channel; } export interface ChannelPinsUpdateEvent extends Event { - event: "CHANNEL_PINS_UPDATE"; - data: { - guild_id?: string; - channel_id: string; - /** - * @format ISO8601 - */ - last_pin_timestamp?: string; - }; + event: "CHANNEL_PINS_UPDATE"; + data: { + guild_id?: string; + channel_id: string; + /** + * @format ISO8601 + */ + last_pin_timestamp?: string; + }; } export interface ChannelRecipientAddEvent extends Event { - event: "CHANNEL_RECIPIENT_ADD"; - data: { - channel_id: string; - user: User; - }; + event: "CHANNEL_RECIPIENT_ADD"; + data: { + channel_id: string; + user: User; + }; } export interface ChannelRecipientRemoveEvent extends Event { - event: "CHANNEL_RECIPIENT_REMOVE"; - data: { - channel_id: string; - user: User; - }; + event: "CHANNEL_RECIPIENT_REMOVE"; + data: { + channel_id: string; + user: User; + }; } export interface GuildCreateEvent extends Event { - event: "GUILD_CREATE"; - data: IReadyGuildDTO & { - joined_at: Date; - // TODO: add them to guild - guild_scheduled_events: never[]; - guild_hashes: unknown; - presences: never[]; - stage_instances: never[]; - threads: never[]; - embedded_activities: never[]; - }; + event: "GUILD_CREATE"; + data: IReadyGuildDTO & { + joined_at: Date; + // TODO: add them to guild + guild_scheduled_events: never[]; + guild_hashes: unknown; + presences: never[]; + stage_instances: never[]; + threads: never[]; + embedded_activities: never[]; + }; } export interface GuildUpdateEvent extends Event { - event: "GUILD_UPDATE"; - data: GuildCreateResponse; + event: "GUILD_UPDATE"; + data: GuildCreateResponse; } export interface GuildDeleteEvent extends Event { - event: "GUILD_DELETE"; - data: { - id: string; - unavailable?: boolean; - }; + event: "GUILD_DELETE"; + data: { + id: string; + unavailable?: boolean; + }; } export interface GuildBanAddEvent extends Event { - event: "GUILD_BAN_ADD"; - data: { - guild_id: string; - user: User; - delete_message_secs?: number; - }; + event: "GUILD_BAN_ADD"; + data: { + guild_id: string; + user: User; + delete_message_secs?: number; + }; } export interface GuildBanRemoveEvent extends Event { - event: "GUILD_BAN_REMOVE"; - data: { - guild_id: string; - user: User; - }; + event: "GUILD_BAN_REMOVE"; + data: { + guild_id: string; + user: User; + }; } export interface GuildEmojisUpdateEvent extends Event { - event: "GUILD_EMOJIS_UPDATE"; - data: { - guild_id: string; - emojis: Emoji[]; - }; + event: "GUILD_EMOJIS_UPDATE"; + data: { + guild_id: string; + emojis: Emoji[]; + }; } export interface GuildStickersUpdateEvent extends Event { - event: "GUILD_STICKERS_UPDATE"; - data: { - guild_id: string; - stickers: Sticker[]; - }; + event: "GUILD_STICKERS_UPDATE"; + data: { + guild_id: string; + stickers: Sticker[]; + }; } export interface GuildIntegrationUpdateEvent extends Event { - event: "GUILD_INTEGRATIONS_UPDATE"; - data: { - guild_id: string; - }; + event: "GUILD_INTEGRATIONS_UPDATE"; + data: { + guild_id: string; + }; } export interface GuildMemberAddEvent extends Event { - event: "GUILD_MEMBER_ADD"; - data: PublicMember & { - guild_id: string; - }; + event: "GUILD_MEMBER_ADD"; + data: PublicMember & { + guild_id: string; + }; } export interface GuildMemberRemoveEvent extends Event { - event: "GUILD_MEMBER_REMOVE"; - data: { - guild_id: string; - user: User; - }; + event: "GUILD_MEMBER_REMOVE"; + data: { + guild_id: string; + user: User; + }; } export interface GuildMemberUpdateEvent extends Event { - event: "GUILD_MEMBER_UPDATE"; - data: { - guild_id: string; - roles: string[]; - user: User; - nick?: string; - joined_at?: Date; - premium_since?: number; - pending?: boolean; - }; + event: "GUILD_MEMBER_UPDATE"; + data: { + guild_id: string; + roles: string[]; + user: User; + nick?: string; + joined_at?: Date; + premium_since?: number; + pending?: boolean; + }; } export interface GuildMembersChunkEvent extends Event { - event: "GUILD_MEMBERS_CHUNK"; - data: { - guild_id: string; - members: PublicMember[]; - chunk_index: number; - chunk_count: number; - not_found?: string[]; - presences?: Presence[]; - nonce?: string; - }; + event: "GUILD_MEMBERS_CHUNK"; + data: { + guild_id: string; + members: PublicMember[]; + chunk_index: number; + chunk_count: number; + not_found?: string[]; + presences?: Presence[]; + nonce?: string; + }; } export interface GuildRoleCreateEvent extends Event { - event: "GUILD_ROLE_CREATE"; - data: { - guild_id: string; - role: Role; - }; + event: "GUILD_ROLE_CREATE"; + data: { + guild_id: string; + role: Role; + }; } export interface GuildRoleUpdateEvent extends Event { - event: "GUILD_ROLE_UPDATE"; - data: { - guild_id: string; - role: Role; - }; + event: "GUILD_ROLE_UPDATE"; + data: { + guild_id: string; + role: Role; + }; } export interface GuildRoleDeleteEvent extends Event { - event: "GUILD_ROLE_DELETE"; - data: { - guild_id: string; - role_id: string; - }; + event: "GUILD_ROLE_DELETE"; + data: { + guild_id: string; + role_id: string; + }; } export interface InviteCreateEvent extends Event { - event: "INVITE_CREATE"; - data: Omit & { - channel_id: string; - guild_id?: string; - }; + event: "INVITE_CREATE"; + data: Omit & { + channel_id: string; + guild_id?: string; + }; } export interface InviteDeleteEvent extends Event { - event: "INVITE_DELETE"; - data: { - channel_id: string; - guild_id?: string; - code: string; - }; + event: "INVITE_DELETE"; + data: { + channel_id: string; + guild_id?: string; + code: string; + }; } export interface MessageCreateEvent extends Event { - event: "MESSAGE_CREATE"; - data: Message; + event: "MESSAGE_CREATE"; + data: Message; } export interface MessageUpdateEvent extends Event { - event: "MESSAGE_UPDATE"; - data: Message; + event: "MESSAGE_UPDATE"; + data: Message; } export interface MessageDeleteEvent extends Event { - event: "MESSAGE_DELETE"; - data: { - id: string; - channel_id: string; - guild_id?: string; - }; + event: "MESSAGE_DELETE"; + data: { + id: string; + channel_id: string; + guild_id?: string; + }; } export interface MessageDeleteBulkEvent extends Event { - event: "MESSAGE_DELETE_BULK"; - data: { - ids: string[]; - channel_id: string; - guild_id?: string; - }; + event: "MESSAGE_DELETE_BULK"; + data: { + ids: string[]; + channel_id: string; + guild_id?: string; + }; } export interface MessageReactionAddEvent extends Event { - event: "MESSAGE_REACTION_ADD"; - data: { - user_id: string; - channel_id: string; - message_id: string; - guild_id?: string; - member?: PublicMember; - emoji: PartialEmoji; - }; + event: "MESSAGE_REACTION_ADD"; + data: { + user_id: string; + channel_id: string; + message_id: string; + guild_id?: string; + member?: PublicMember; + emoji: PartialEmoji; + }; } export interface MessageReactionRemoveEvent extends Event { - event: "MESSAGE_REACTION_REMOVE"; - data: { - user_id: string; - channel_id: string; - message_id: string; - guild_id?: string; - emoji: PartialEmoji; - }; + event: "MESSAGE_REACTION_REMOVE"; + data: { + user_id: string; + channel_id: string; + message_id: string; + guild_id?: string; + emoji: PartialEmoji; + }; } export interface MessageReactionRemoveAllEvent extends Event { - event: "MESSAGE_REACTION_REMOVE_ALL"; - data: { - channel_id: string; - message_id: string; - guild_id?: string; - }; + event: "MESSAGE_REACTION_REMOVE_ALL"; + data: { + channel_id: string; + message_id: string; + guild_id?: string; + }; } export interface MessageReactionRemoveEmojiEvent extends Event { - event: "MESSAGE_REACTION_REMOVE_EMOJI"; - data: { - channel_id: string; - message_id: string; - guild_id?: string; - emoji: PartialEmoji; - }; + event: "MESSAGE_REACTION_REMOVE_EMOJI"; + data: { + channel_id: string; + message_id: string; + guild_id?: string; + emoji: PartialEmoji; + }; } export interface PresenceUpdateEvent extends Event { - event: "PRESENCE_UPDATE"; - data: Presence; + event: "PRESENCE_UPDATE"; + data: Presence; } export interface TypingStartEvent extends Event { - event: "TYPING_START"; - data: { - channel_id: string; - user_id: string; - timestamp: number; - guild_id?: string; - member?: PublicMember; - }; + event: "TYPING_START"; + data: { + channel_id: string; + user_id: string; + timestamp: number; + guild_id?: string; + member?: PublicMember; + }; } export interface UserUpdateEvent extends Event { - event: "USER_UPDATE"; - data: Omit; + event: "USER_UPDATE"; + data: Omit; } export interface UserDeleteEvent extends Event { - event: "USER_DELETE"; - data: { - user_id: string; - }; + event: "USER_DELETE"; + data: { + user_id: string; + }; } export interface UserConnectionsUpdateEvent extends Event { - event: "USER_CONNECTIONS_UPDATE"; + event: "USER_CONNECTIONS_UPDATE"; } export interface VoiceStateUpdateEvent extends Event { - event: "VOICE_STATE_UPDATE"; - data: PublicVoiceState & { - member: PublicMember; - }; + event: "VOICE_STATE_UPDATE"; + data: PublicVoiceState & { + member: PublicMember; + }; } export interface VoiceServerUpdateEvent extends Event { - event: "VOICE_SERVER_UPDATE"; - data: { - token: string; - guild_id: string | null; - endpoint: string; - channel_id?: string; - }; + event: "VOICE_SERVER_UPDATE"; + data: { + token: string; + guild_id: string | null; + endpoint: string; + channel_id?: string; + }; } export interface StreamCreateEvent extends Event { - event: "STREAM_CREATE"; - data: { - stream_key: string; - rtc_server_id: string; - viewer_ids: string[]; - region: string; - paused: boolean; - }; + event: "STREAM_CREATE"; + data: { + stream_key: string; + rtc_server_id: string; + viewer_ids: string[]; + region: string; + paused: boolean; + }; } export interface StreamServerUpdateEvent extends Event { - event: "STREAM_SERVER_UPDATE"; - data: { - token: string; - stream_key: string; - endpoint: string; - guild_id: string | null; - }; + event: "STREAM_SERVER_UPDATE"; + data: { + token: string; + stream_key: string; + endpoint: string; + guild_id: string | null; + }; } export interface StreamDeleteEvent extends Event { - event: "STREAM_DELETE"; - data: { - stream_key: string; - }; + event: "STREAM_DELETE"; + data: { + stream_key: string; + }; } export interface WebhooksUpdateEvent extends Event { - event: "WEBHOOKS_UPDATE"; - data: { - guild_id: string; - channel_id: string; - }; + event: "WEBHOOKS_UPDATE"; + data: { + guild_id: string; + channel_id: string; + }; } export type ApplicationCommandPayload = ApplicationCommand & { - guild_id: string; + guild_id: string; }; export interface ApplicationCommandCreateEvent extends Event { - event: "APPLICATION_COMMAND_CREATE"; - data: ApplicationCommandPayload; + event: "APPLICATION_COMMAND_CREATE"; + data: ApplicationCommandPayload; } export interface ApplicationCommandUpdateEvent extends Event { - event: "APPLICATION_COMMAND_UPDATE"; - data: ApplicationCommandPayload; + event: "APPLICATION_COMMAND_UPDATE"; + data: ApplicationCommandPayload; } export interface ApplicationCommandDeleteEvent extends Event { - event: "APPLICATION_COMMAND_DELETE"; - data: ApplicationCommandPayload; + event: "APPLICATION_COMMAND_DELETE"; + data: ApplicationCommandPayload; } export interface InteractionCreateEvent extends Event { - event: "INTERACTION_CREATE"; - data: - | Interaction - | { - id: Snowflake; - nonce?: string; - }; + event: "INTERACTION_CREATE"; + data: + | Interaction + | { + id: Snowflake; + nonce?: string; + }; } export interface InteractionSuccessEvent extends Event { - event: "INTERACTION_SUCCESS"; - data: { - id: Snowflake; - nonce: string; - }; + event: "INTERACTION_SUCCESS"; + data: { + id: Snowflake; + nonce: string; + }; } export interface InteractionFailureEvent extends Event { - event: "INTERACTION_FAILURE"; - data: { - id: Snowflake; - nonce?: string; - reason_code: number; // TODO: types? - }; + event: "INTERACTION_FAILURE"; + data: { + id: Snowflake; + nonce?: string; + reason_code: number; // TODO: types? + }; } export interface MessageAckEvent extends Event { - event: "MESSAGE_ACK"; - data: { - channel_id: string; - message_id: string; - version?: number; - manual?: boolean; - mention_count?: number; - }; + event: "MESSAGE_ACK"; + data: { + channel_id: string; + message_id: string; + version?: number; + manual?: boolean; + mention_count?: number; + }; } export interface RelationshipAddEvent extends Event { - event: "RELATIONSHIP_ADD"; - data: PublicRelationship & { - should_notify?: boolean; - user: PublicUser; - }; + event: "RELATIONSHIP_ADD"; + data: PublicRelationship & { + should_notify?: boolean; + user: PublicUser; + }; } export interface RelationshipRemoveEvent extends Event { - event: "RELATIONSHIP_REMOVE"; - data: Omit; + event: "RELATIONSHIP_REMOVE"; + data: Omit; } export interface GatewaySessionClientInfo { - version: number; - os: string; - client: string; + version: number; + os: string; + client: string; } export interface GatewaySession { - session_id: string; - activities: Activity[]; - hidden_activities: Activity[]; - client_info: GatewaySessionClientInfo; - status: Status; - active?: boolean; // How is this even defined? + session_id: string; + activities: Activity[]; + hidden_activities: Activity[]; + client_info: GatewaySessionClientInfo; + status: Status; + active?: boolean; // How is this even defined? } export interface SessionsReplace extends Event { - event: "SESSIONS_REPLACE"; - data: GatewaySession[]; + event: "SESSIONS_REPLACE"; + data: GatewaySession[]; } export interface GuildMemberListUpdate extends Event { - event: "GUILD_MEMBER_LIST_UPDATE"; - data: { - groups: { id: string; count: number }[]; - guild_id: string; - id: string; - member_count: number; - online_count: number; - ops: { - index: number; - item: { - member?: PublicMember & { presence: Presence }; - group?: { id: string; count: number }[]; - }; - }[]; - }; + event: "GUILD_MEMBER_LIST_UPDATE"; + data: { + groups: { id: string; count: number }[]; + guild_id: string; + id: string; + member_count: number; + online_count: number; + ops: { + index: number; + item: { + member?: PublicMember & { presence: Presence }; + group?: { id: string; count: number }[]; + }; + }[]; + }; } export type EventData = - | InvalidatedEvent - | ReadyEvent - | ChannelCreateEvent - | ChannelUpdateEvent - | ChannelDeleteEvent - | ChannelPinsUpdateEvent - | ChannelRecipientAddEvent - | ChannelRecipientRemoveEvent - | GuildCreateEvent - | GuildUpdateEvent - | GuildDeleteEvent - | GuildBanAddEvent - | GuildBanRemoveEvent - | GuildEmojisUpdateEvent - | GuildIntegrationUpdateEvent - | GuildMemberAddEvent - | GuildMemberRemoveEvent - | GuildMemberUpdateEvent - | GuildMembersChunkEvent - | GuildMemberListUpdate - | GuildRoleCreateEvent - | GuildRoleUpdateEvent - | GuildRoleDeleteEvent - | InviteCreateEvent - | InviteDeleteEvent - | MessageCreateEvent - | MessageUpdateEvent - | MessageDeleteEvent - | MessageDeleteBulkEvent - | MessageReactionAddEvent - | MessageReactionRemoveEvent - | MessageReactionRemoveAllEvent - | MessageReactionRemoveEmojiEvent - | PresenceUpdateEvent - | TypingStartEvent - | UserUpdateEvent - | UserDeleteEvent - | UserConnectionsUpdateEvent - | VoiceStateUpdateEvent - | VoiceServerUpdateEvent - | WebhooksUpdateEvent - | ApplicationCommandCreateEvent - | ApplicationCommandUpdateEvent - | ApplicationCommandDeleteEvent - | InteractionCreateEvent - | InteractionSuccessEvent - | InteractionFailureEvent - | MessageAckEvent - | RelationshipAddEvent - | RelationshipRemoveEvent; + | InvalidatedEvent + | ReadyEvent + | ChannelCreateEvent + | ChannelUpdateEvent + | ChannelDeleteEvent + | ChannelPinsUpdateEvent + | ChannelRecipientAddEvent + | ChannelRecipientRemoveEvent + | GuildCreateEvent + | GuildUpdateEvent + | GuildDeleteEvent + | GuildBanAddEvent + | GuildBanRemoveEvent + | GuildEmojisUpdateEvent + | GuildIntegrationUpdateEvent + | GuildMemberAddEvent + | GuildMemberRemoveEvent + | GuildMemberUpdateEvent + | GuildMembersChunkEvent + | GuildMemberListUpdate + | GuildRoleCreateEvent + | GuildRoleUpdateEvent + | GuildRoleDeleteEvent + | InviteCreateEvent + | InviteDeleteEvent + | MessageCreateEvent + | MessageUpdateEvent + | MessageDeleteEvent + | MessageDeleteBulkEvent + | MessageReactionAddEvent + | MessageReactionRemoveEvent + | MessageReactionRemoveAllEvent + | MessageReactionRemoveEmojiEvent + | PresenceUpdateEvent + | TypingStartEvent + | UserUpdateEvent + | UserDeleteEvent + | UserConnectionsUpdateEvent + | VoiceStateUpdateEvent + | VoiceServerUpdateEvent + | WebhooksUpdateEvent + | ApplicationCommandCreateEvent + | ApplicationCommandUpdateEvent + | ApplicationCommandDeleteEvent + | InteractionCreateEvent + | InteractionSuccessEvent + | InteractionFailureEvent + | MessageAckEvent + | RelationshipAddEvent + | RelationshipRemoveEvent; // located in collection events export enum EVENTEnum { - Ready = "READY", - ReadySupplemental = "READY_SUPPLEMENTAL", - ChannelCreate = "CHANNEL_CREATE", - ChannelUpdate = "CHANNEL_UPDATE", - ChannelDelete = "CHANNEL_DELETE", - ChannelPinsUpdate = "CHANNEL_PINS_UPDATE", - ChannelRecipientAdd = "CHANNEL_RECIPIENT_ADD", - ChannelRecipientRemove = "CHANNEL_RECIPIENT_REMOVE", - GuildCreate = "GUILD_CREATE", - GuildUpdate = "GUILD_UPDATE", - GuildDelete = "GUILD_DELETE", - GuildBanAdd = "GUILD_BAN_ADD", - GuildBanRemove = "GUILD_BAN_REMOVE", - GuildEmojUpdate = "GUILD_EMOJI_UPDATE", - GuildIntegrationsUpdate = "GUILD_INTEGRATIONS_UPDATE", - GuildMemberAdd = "GUILD_MEMBER_ADD", - GuildMemberRempve = "GUILD_MEMBER_REMOVE", - GuildMemberUpdate = "GUILD_MEMBER_UPDATE", - GuildMemberSpeaking = "GUILD_MEMBER_SPEAKING", - GuildMembersChunk = "GUILD_MEMBERS_CHUNK", - GuildMemberListUpdate = "GUILD_MEMBER_LIST_UPDATE", - GuildRoleCreate = "GUILD_ROLE_CREATE", - GuildRoleDelete = "GUILD_ROLE_DELETE", - GuildRoleUpdate = "GUILD_ROLE_UPDATE", - InviteCreate = "INVITE_CREATE", - InviteDelete = "INVITE_DELETE", - MessageCreate = "MESSAGE_CREATE", - MessageUpdate = "MESSAGE_UPDATE", - MessageDelete = "MESSAGE_DELETE", - MessageDeleteBulk = "MESSAGE_DELETE_BULK", - MessageReactionAdd = "MESSAGE_REACTION_ADD", - MessageReactionRemove = "MESSAGE_REACTION_REMOVE", - MessageReactionRemoveAll = "MESSAGE_REACTION_REMOVE_ALL", - MessageReactionRemoveEmoji = "MESSAGE_REACTION_REMOVE_EMOJI", - PresenceUpdate = "PRESENCE_UPDATE", - TypingStart = "TYPING_START", - UserUpdate = "USER_UPDATE", - UserDelete = "USER_DELETE", - UserConnectionsUpdate = "USER_CONNECTIONS_UPDATE", - WebhooksUpdate = "WEBHOOKS_UPDATE", - InteractionCreate = "INTERACTION_CREATE", - InteractionSuccess = "INTERACTION_SUCCESS", - InteractionFailure = "INTERACTION_FAILURE", - VoiceStateUpdate = "VOICE_STATE_UPDATE", - VoiceServerUpdate = "VOICE_SERVER_UPDATE", - ApplicationCommandCreate = "APPLICATION_COMMAND_CREATE", - ApplicationCommandUpdate = "APPLICATION_COMMAND_UPDATE", - ApplicationCommandDelete = "APPLICATION_COMMAND_DELETE", - SessionsReplace = "SESSIONS_REPLACE", + Ready = "READY", + ReadySupplemental = "READY_SUPPLEMENTAL", + ChannelCreate = "CHANNEL_CREATE", + ChannelUpdate = "CHANNEL_UPDATE", + ChannelDelete = "CHANNEL_DELETE", + ChannelPinsUpdate = "CHANNEL_PINS_UPDATE", + ChannelRecipientAdd = "CHANNEL_RECIPIENT_ADD", + ChannelRecipientRemove = "CHANNEL_RECIPIENT_REMOVE", + GuildCreate = "GUILD_CREATE", + GuildUpdate = "GUILD_UPDATE", + GuildDelete = "GUILD_DELETE", + GuildBanAdd = "GUILD_BAN_ADD", + GuildBanRemove = "GUILD_BAN_REMOVE", + GuildEmojUpdate = "GUILD_EMOJI_UPDATE", + GuildIntegrationsUpdate = "GUILD_INTEGRATIONS_UPDATE", + GuildMemberAdd = "GUILD_MEMBER_ADD", + GuildMemberRempve = "GUILD_MEMBER_REMOVE", + GuildMemberUpdate = "GUILD_MEMBER_UPDATE", + GuildMemberSpeaking = "GUILD_MEMBER_SPEAKING", + GuildMembersChunk = "GUILD_MEMBERS_CHUNK", + GuildMemberListUpdate = "GUILD_MEMBER_LIST_UPDATE", + GuildRoleCreate = "GUILD_ROLE_CREATE", + GuildRoleDelete = "GUILD_ROLE_DELETE", + GuildRoleUpdate = "GUILD_ROLE_UPDATE", + InviteCreate = "INVITE_CREATE", + InviteDelete = "INVITE_DELETE", + MessageCreate = "MESSAGE_CREATE", + MessageUpdate = "MESSAGE_UPDATE", + MessageDelete = "MESSAGE_DELETE", + MessageDeleteBulk = "MESSAGE_DELETE_BULK", + MessageReactionAdd = "MESSAGE_REACTION_ADD", + MessageReactionRemove = "MESSAGE_REACTION_REMOVE", + MessageReactionRemoveAll = "MESSAGE_REACTION_REMOVE_ALL", + MessageReactionRemoveEmoji = "MESSAGE_REACTION_REMOVE_EMOJI", + PresenceUpdate = "PRESENCE_UPDATE", + TypingStart = "TYPING_START", + UserUpdate = "USER_UPDATE", + UserDelete = "USER_DELETE", + UserConnectionsUpdate = "USER_CONNECTIONS_UPDATE", + WebhooksUpdate = "WEBHOOKS_UPDATE", + InteractionCreate = "INTERACTION_CREATE", + InteractionSuccess = "INTERACTION_SUCCESS", + InteractionFailure = "INTERACTION_FAILURE", + VoiceStateUpdate = "VOICE_STATE_UPDATE", + VoiceServerUpdate = "VOICE_SERVER_UPDATE", + ApplicationCommandCreate = "APPLICATION_COMMAND_CREATE", + ApplicationCommandUpdate = "APPLICATION_COMMAND_UPDATE", + ApplicationCommandDelete = "APPLICATION_COMMAND_DELETE", + SessionsReplace = "SESSIONS_REPLACE", } export type EVENT = - | "READY" - | "CHANNEL_CREATE" - | "CHANNEL_UPDATE" - | "CHANNEL_DELETE" - | "CHANNEL_PINS_UPDATE" - | "CHANNEL_RECIPIENT_ADD" - | "CHANNEL_RECIPIENT_REMOVE" - | "GUILD_CREATE" - | "GUILD_UPDATE" - | "GUILD_DELETE" - | "GUILD_BAN_ADD" - | "GUILD_BAN_REMOVE" - | "GUILD_EMOJIS_UPDATE" - | "GUILD_STICKERS_UPDATE" - | "GUILD_INTEGRATIONS_UPDATE" - | "GUILD_MEMBER_ADD" - | "GUILD_MEMBER_REMOVE" - | "GUILD_MEMBER_UPDATE" - | "GUILD_MEMBER_SPEAKING" - | "GUILD_MEMBERS_CHUNK" - | "GUILD_MEMBER_LIST_UPDATE" - | "GUILD_ROLE_CREATE" - | "GUILD_ROLE_DELETE" - | "GUILD_ROLE_UPDATE" - | "INVITE_CREATE" - | "INVITE_DELETE" - | "MESSAGE_CREATE" - | "MESSAGE_UPDATE" - | "MESSAGE_DELETE" - | "MESSAGE_DELETE_BULK" - | "MESSAGE_REACTION_ADD" - // TODO: add a new event: bulk add reaction: - // | "MESSAGE_REACTION_BULK_ADD" - | "MESSAGE_REACTION_REMOVE" - | "MESSAGE_REACTION_REMOVE_ALL" - | "MESSAGE_REACTION_REMOVE_EMOJI" - | "PRESENCE_UPDATE" - | "TYPING_START" - | "USER_UPDATE" - | "USER_DELETE" - | "USER_CONNECTIONS_UPDATE" - | "USER_NOTE_UPDATE" - | "WEBHOOKS_UPDATE" - | "INTERACTION_CREATE" - | "INTERACTION_SUCCESS" - | "INTERACTION_FAILURE" - | "VOICE_STATE_UPDATE" - | "VOICE_SERVER_UPDATE" - | "STREAM_CREATE" - | "STREAM_SERVER_UPDATE" - | "STREAM_DELETE" - | "APPLICATION_COMMAND_CREATE" - | "APPLICATION_COMMAND_UPDATE" - | "APPLICATION_COMMAND_DELETE" - | "MESSAGE_ACK" - | "RELATIONSHIP_ADD" - | "RELATIONSHIP_REMOVE" - | "SESSIONS_REPLACE" - | "USER_SETTINGS_PROTO_UPDATE" - | CUSTOMEVENTS; + | "READY" + | "CHANNEL_CREATE" + | "CHANNEL_UPDATE" + | "CHANNEL_DELETE" + | "CHANNEL_PINS_UPDATE" + | "CHANNEL_RECIPIENT_ADD" + | "CHANNEL_RECIPIENT_REMOVE" + | "GUILD_CREATE" + | "GUILD_UPDATE" + | "GUILD_DELETE" + | "GUILD_BAN_ADD" + | "GUILD_BAN_REMOVE" + | "GUILD_EMOJIS_UPDATE" + | "GUILD_STICKERS_UPDATE" + | "GUILD_INTEGRATIONS_UPDATE" + | "GUILD_MEMBER_ADD" + | "GUILD_MEMBER_REMOVE" + | "GUILD_MEMBER_UPDATE" + | "GUILD_MEMBER_SPEAKING" + | "GUILD_MEMBERS_CHUNK" + | "GUILD_MEMBER_LIST_UPDATE" + | "GUILD_ROLE_CREATE" + | "GUILD_ROLE_DELETE" + | "GUILD_ROLE_UPDATE" + | "INVITE_CREATE" + | "INVITE_DELETE" + | "MESSAGE_CREATE" + | "MESSAGE_UPDATE" + | "MESSAGE_DELETE" + | "MESSAGE_DELETE_BULK" + | "MESSAGE_REACTION_ADD" + // TODO: add a new event: bulk add reaction: + // | "MESSAGE_REACTION_BULK_ADD" + | "MESSAGE_REACTION_REMOVE" + | "MESSAGE_REACTION_REMOVE_ALL" + | "MESSAGE_REACTION_REMOVE_EMOJI" + | "PRESENCE_UPDATE" + | "TYPING_START" + | "USER_UPDATE" + | "USER_DELETE" + | "USER_CONNECTIONS_UPDATE" + | "USER_NOTE_UPDATE" + | "WEBHOOKS_UPDATE" + | "INTERACTION_CREATE" + | "INTERACTION_SUCCESS" + | "INTERACTION_FAILURE" + | "VOICE_STATE_UPDATE" + | "VOICE_SERVER_UPDATE" + | "STREAM_CREATE" + | "STREAM_SERVER_UPDATE" + | "STREAM_DELETE" + | "APPLICATION_COMMAND_CREATE" + | "APPLICATION_COMMAND_UPDATE" + | "APPLICATION_COMMAND_DELETE" + | "MESSAGE_ACK" + | "RELATIONSHIP_ADD" + | "RELATIONSHIP_REMOVE" + | "SESSIONS_REPLACE" + | "USER_SETTINGS_PROTO_UPDATE" + | CUSTOMEVENTS; export type CUSTOMEVENTS = "INVALIDATED" | "RATELIMIT"; diff --git a/src/util/interfaces/GuildWelcomeScreen.ts b/src/util/interfaces/GuildWelcomeScreen.ts index 38b6061..d4de413 100644 --- a/src/util/interfaces/GuildWelcomeScreen.ts +++ b/src/util/interfaces/GuildWelcomeScreen.ts @@ -1,10 +1,10 @@ export interface GuildWelcomeScreen { - enabled: boolean; - description: string; - welcome_channels: { - description: string; - emoji_id?: string; - emoji_name?: string; - channel_id: string; - }[]; + enabled: boolean; + description: string; + welcome_channels: { + description: string; + emoji_id?: string; + emoji_name?: string; + channel_id: string; + }[]; } diff --git a/src/util/interfaces/Interaction.ts b/src/util/interfaces/Interaction.ts index 09068df..8c9c752 100644 --- a/src/util/interfaces/Interaction.ts +++ b/src/util/interfaces/Interaction.ts @@ -19,36 +19,36 @@ import { Embed, AllowedMentions } from "@spacebar/schemas"; export interface Interaction { - id: string; - type: InteractionType; - data?: object; // TODO typing - guild_id: string; - channel_id: string; - member_id: string; - token: string; - version: number; + id: string; + type: InteractionType; + data?: object; // TODO typing + guild_id: string; + channel_id: string; + member_id: string; + token: string; + version: number; } export enum InteractionType { - Ping = 1, - ApplicationCommand = 2, - MessageComponent = 3, - ApplicationCommandAutocomplete = 4, - ModalSubmit = 5, + Ping = 1, + ApplicationCommand = 2, + MessageComponent = 3, + ApplicationCommandAutocomplete = 4, + ModalSubmit = 5, } export enum InteractionResponseType { - SelfCommandResponse = 0, - Pong = 1, - Acknowledge = 2, - ChannelMessage = 3, - ChannelMessageWithSource = 4, - AcknowledgeWithSource = 5, + SelfCommandResponse = 0, + Pong = 1, + Acknowledge = 2, + ChannelMessage = 3, + ChannelMessageWithSource = 4, + AcknowledgeWithSource = 5, } export interface InteractionApplicationCommandCallbackData { - tts?: boolean; - content: string; - embeds?: Embed[]; - allowed_mentions?: AllowedMentions; + tts?: boolean; + content: string; + embeds?: Embed[]; + allowed_mentions?: AllowedMentions; } diff --git a/src/util/interfaces/Presence.ts b/src/util/interfaces/Presence.ts index d04a6e6..40aca8d 100644 --- a/src/util/interfaces/Presence.ts +++ b/src/util/interfaces/Presence.ts @@ -21,10 +21,10 @@ import { PublicUser } from "@spacebar/schemas"; export interface Presence { - user: PublicUser; - guild_id?: string; - status: Status; - activities: Activity[]; - client_status: ClientStatus; - // TODO: game + user: PublicUser; + guild_id?: string; + status: Status; + activities: Activity[]; + client_status: ClientStatus; + // TODO: game } diff --git a/src/util/interfaces/Status.ts b/src/util/interfaces/Status.ts index 0f2f4e1..4ee0b84 100644 --- a/src/util/interfaces/Status.ts +++ b/src/util/interfaces/Status.ts @@ -19,8 +19,8 @@ export type Status = "idle" | "dnd" | "online" | "offline" | "invisible"; export interface ClientStatus { - desktop?: string; // e.g. Windows/Linux/Mac - mobile?: string; // e.g. iOS/Android - web?: string; // e.g. browser, bot account, unknown - embedded?: string; // e.g. embedded + desktop?: string; // e.g. Windows/Linux/Mac + mobile?: string; // e.g. iOS/Android + web?: string; // e.g. browser, bot account, unknown + embedded?: string; // e.g. embedded } diff --git a/src/util/migration/postgres/1673609867556-templateDeleteCascade.ts b/src/util/migration/postgres/1673609867556-templateDeleteCascade.ts index ba0025d..dbeef99 100644 --- a/src/util/migration/postgres/1673609867556-templateDeleteCascade.ts +++ b/src/util/migration/postgres/1673609867556-templateDeleteCascade.ts @@ -19,19 +19,19 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class templateDeleteCascade1673609867556 implements MigrationInterface { - name = "templateDeleteCascade1673609867556"; + name = "templateDeleteCascade1673609867556"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "templates" DROP CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11"`); - await queryRunner.query( - `ALTER TABLE "templates" ADD CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11" FOREIGN KEY ("source_guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "templates" DROP CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11"`); + await queryRunner.query( + `ALTER TABLE "templates" ADD CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11" FOREIGN KEY ("source_guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "templates" DROP CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11"`); - await queryRunner.query( - `ALTER TABLE "templates" ADD CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11" FOREIGN KEY ("source_guild_id") REFERENCES "guilds"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, - ); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "templates" DROP CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11"`); + await queryRunner.query( + `ALTER TABLE "templates" ADD CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11" FOREIGN KEY ("source_guild_id") REFERENCES "guilds"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } } diff --git a/src/util/migration/postgres/1675044825710-webauthn.ts b/src/util/migration/postgres/1675044825710-webauthn.ts index 19a96b4..5c052cc 100644 --- a/src/util/migration/postgres/1675044825710-webauthn.ts +++ b/src/util/migration/postgres/1675044825710-webauthn.ts @@ -19,21 +19,21 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class webauthn1675044825710 implements MigrationInterface { - name = "webauthn1675044825710"; + name = "webauthn1675044825710"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "security_keys" ("id" character varying NOT NULL, "user_id" character varying, "key_id" character varying NOT NULL, "public_key" character varying NOT NULL, "counter" integer NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_6e95cdd91779e7cca06d1fff89c" PRIMARY KEY ("id"))`, - ); - await queryRunner.query(`ALTER TABLE "users" ADD "webauthn_enabled" boolean NOT NULL DEFAULT false`); - await queryRunner.query( - `ALTER TABLE "security_keys" ADD CONSTRAINT "FK_24c97d0771cafedce6d7163eaad" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "security_keys" ("id" character varying NOT NULL, "user_id" character varying, "key_id" character varying NOT NULL, "public_key" character varying NOT NULL, "counter" integer NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_6e95cdd91779e7cca06d1fff89c" PRIMARY KEY ("id"))`, + ); + await queryRunner.query(`ALTER TABLE "users" ADD "webauthn_enabled" boolean NOT NULL DEFAULT false`); + await queryRunner.query( + `ALTER TABLE "security_keys" ADD CONSTRAINT "FK_24c97d0771cafedce6d7163eaad" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "security_keys" DROP CONSTRAINT "FK_24c97d0771cafedce6d7163eaad"`); - await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "webauthn_enabled"`); - await queryRunner.query(`DROP TABLE "security_keys"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "security_keys" DROP CONSTRAINT "FK_24c97d0771cafedce6d7163eaad"`); + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "webauthn_enabled"`); + await queryRunner.query(`DROP TABLE "security_keys"`); + } } diff --git a/src/util/migration/postgres/1696420827239-guildChannelOrdering.ts b/src/util/migration/postgres/1696420827239-guildChannelOrdering.ts index 4848ffd..2323ae6 100644 --- a/src/util/migration/postgres/1696420827239-guildChannelOrdering.ts +++ b/src/util/migration/postgres/1696420827239-guildChannelOrdering.ts @@ -1,38 +1,38 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class guildChannelOrdering1696420827239 implements MigrationInterface { - name = "guildChannelOrdering1696420827239"; + name = "guildChannelOrdering1696420827239"; - public async up(queryRunner: QueryRunner): Promise { - const guilds = await queryRunner.query(`SELECT id FROM guilds`, undefined, true); + public async up(queryRunner: QueryRunner): Promise { + const guilds = await queryRunner.query(`SELECT id FROM guilds`, undefined, true); - await queryRunner.query(`ALTER TABLE guilds ADD channel_ordering text NOT NULL DEFAULT '[]'`); + await queryRunner.query(`ALTER TABLE guilds ADD channel_ordering text NOT NULL DEFAULT '[]'`); - for (const guild_id of guilds.records.map((x) => x.id)) { - const channels: Array<{ position: number; id: string }> = (await queryRunner.query(`SELECT id, position FROM channels WHERE guild_id = $1`, [guild_id], true)).records; + for (const guild_id of guilds.records.map((x) => x.id)) { + const channels: Array<{ position: number; id: string }> = (await queryRunner.query(`SELECT id, position FROM channels WHERE guild_id = $1`, [guild_id], true)).records; - channels.sort((a, b) => a.position - b.position); + channels.sort((a, b) => a.position - b.position); - await queryRunner.query(`UPDATE guilds SET channel_ordering = $1 WHERE id = $2`, [JSON.stringify(channels.map((x) => x.id)), guild_id]); - } + await queryRunner.query(`UPDATE guilds SET channel_ordering = $1 WHERE id = $2`, [JSON.stringify(channels.map((x) => x.id)), guild_id]); + } - await queryRunner.query(`ALTER TABLE channels DROP COLUMN position`); - } + await queryRunner.query(`ALTER TABLE channels DROP COLUMN position`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE channels ADD position integer NOT NULL DEFAULT 0`); + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE channels ADD position integer NOT NULL DEFAULT 0`); - const guilds = await queryRunner.query(`SELECT id, channel_ordering FROM guilds`, undefined, true); + const guilds = await queryRunner.query(`SELECT id, channel_ordering FROM guilds`, undefined, true); - for (const guild of guilds.records) { - const channel_ordering: string[] = JSON.parse(guild.channel_ordering); + for (const guild of guilds.records) { + const channel_ordering: string[] = JSON.parse(guild.channel_ordering); - for (let i = 0; i < channel_ordering.length; i++) { - const channel_id = channel_ordering[i]; - await queryRunner.query(`UPDATE channels SET position = $1 WHERE id = $2`, [i, channel_id]); - } - } + for (let i = 0; i < channel_ordering.length; i++) { + const channel_id = channel_ordering[i]; + await queryRunner.query(`UPDATE channels SET position = $1 WHERE id = $2`, [i, channel_id]); + } + } - await queryRunner.query(`ALTER TABLE guilds DROP COLUMN channel_ordering`); - } + await queryRunner.query(`ALTER TABLE guilds DROP COLUMN channel_ordering`); + } } diff --git a/src/util/migration/postgres/1713116476900-messageFlagsNotNull.ts b/src/util/migration/postgres/1713116476900-messageFlagsNotNull.ts index afec685..57948bc 100644 --- a/src/util/migration/postgres/1713116476900-messageFlagsNotNull.ts +++ b/src/util/migration/postgres/1713116476900-messageFlagsNotNull.ts @@ -1,19 +1,19 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class MessageFlagsNotNull1713116476900 implements MigrationInterface { - name = "MessageFlagsNotNull1713116476900"; + name = "MessageFlagsNotNull1713116476900"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE messages RENAME COLUMN flags TO flags_old;"); - await queryRunner.query("ALTER TABLE messages ADD COLUMN flags integer NOT NULL DEFAULT 0;"); - await queryRunner.query("UPDATE messages SET flags = COALESCE(flags_old, 0);"); - await queryRunner.query("ALTER TABLE messages DROP COLUMN flags_old;"); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE messages RENAME COLUMN flags TO flags_old;"); + await queryRunner.query("ALTER TABLE messages ADD COLUMN flags integer NOT NULL DEFAULT 0;"); + await queryRunner.query("UPDATE messages SET flags = COALESCE(flags_old, 0);"); + await queryRunner.query("ALTER TABLE messages DROP COLUMN flags_old;"); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE messages RENAME COLUMN flags TO flags_new;"); - await queryRunner.query("ALTER TABLE messages ADD COLUMN flags integer;"); - await queryRunner.query("UPDATE messages SET flags = flags_new;"); - await queryRunner.query("ALTER TABLE messages DROP COLUMN flags_new;"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE messages RENAME COLUMN flags TO flags_new;"); + await queryRunner.query("ALTER TABLE messages ADD COLUMN flags integer;"); + await queryRunner.query("UPDATE messages SET flags = flags_new;"); + await queryRunner.query("ALTER TABLE messages DROP COLUMN flags_new;"); + } } diff --git a/src/util/migration/postgres/1719776735000-newUserSettings.ts b/src/util/migration/postgres/1719776735000-newUserSettings.ts index d4142d2..cd73037 100644 --- a/src/util/migration/postgres/1719776735000-newUserSettings.ts +++ b/src/util/migration/postgres/1719776735000-newUserSettings.ts @@ -1,15 +1,15 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class NewUserSettings1719776735000 implements MigrationInterface { - name = "NewUserSettings1719776735000"; + name = "NewUserSettings1719776735000"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE user_settings ADD COLUMN friend_discovery_flags integer DEFAULT 0;"); - await queryRunner.query("ALTER TABLE user_settings ADD COLUMN view_nsfw_guilds boolean DEFAULT true;"); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE user_settings ADD COLUMN friend_discovery_flags integer DEFAULT 0;"); + await queryRunner.query("ALTER TABLE user_settings ADD COLUMN view_nsfw_guilds boolean DEFAULT true;"); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE user_settings DROP COLUMN friend_discovery_flags;"); - await queryRunner.query("ALTER TABLE user_settings DROP COLUMN view_nsfw_guilds;"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE user_settings DROP COLUMN friend_discovery_flags;"); + await queryRunner.query("ALTER TABLE user_settings DROP COLUMN view_nsfw_guilds;"); + } } diff --git a/src/util/migration/postgres/1720157926878-messagePollObject.ts b/src/util/migration/postgres/1720157926878-messagePollObject.ts index 7c3c95a..bd21c6e 100644 --- a/src/util/migration/postgres/1720157926878-messagePollObject.ts +++ b/src/util/migration/postgres/1720157926878-messagePollObject.ts @@ -1,13 +1,13 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class MessagePollObject1720157926878 implements MigrationInterface { - name = "MessagePollObject1720157926878"; + name = "MessagePollObject1720157926878"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE messages ADD poll text NULL"); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE messages ADD poll text NULL"); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE messages DROP COLUMN poll"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE messages DROP COLUMN poll"); + } } diff --git a/src/util/migration/postgres/1720628601997-badges.ts b/src/util/migration/postgres/1720628601997-badges.ts index 0cecb6e..7fe1e19 100644 --- a/src/util/migration/postgres/1720628601997-badges.ts +++ b/src/util/migration/postgres/1720628601997-badges.ts @@ -1,17 +1,17 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class Badges1720628601997 implements MigrationInterface { - name = "Badges1720628601997"; + name = "Badges1720628601997"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "badges" ("id" character varying NOT NULL, "description" character varying NOT NULL, "icon" character varying NOT NULL, "link" character varying, CONSTRAINT "PK_8a651318b8de577e8e217676466" PRIMARY KEY ("id"))`, - ); - await queryRunner.query(`ALTER TABLE "users" ADD "badge_ids" text`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "badges" ("id" character varying NOT NULL, "description" character varying NOT NULL, "icon" character varying NOT NULL, "link" character varying, CONSTRAINT "PK_8a651318b8de577e8e217676466" PRIMARY KEY ("id"))`, + ); + await queryRunner.query(`ALTER TABLE "users" ADD "badge_ids" text`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "badge_ids"`); - await queryRunner.query(`DROP TABLE "badges"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "badge_ids"`); + await queryRunner.query(`DROP TABLE "badges"`); + } } diff --git a/src/util/migration/postgres/1721298824927-webhookMessageProperties.ts b/src/util/migration/postgres/1721298824927-webhookMessageProperties.ts index 46c507d..3576e4a 100644 --- a/src/util/migration/postgres/1721298824927-webhookMessageProperties.ts +++ b/src/util/migration/postgres/1721298824927-webhookMessageProperties.ts @@ -1,15 +1,15 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class WebhookMessageProperties1721298824927 implements MigrationInterface { - name = "WebhookMessageProperties1721298824927"; + name = "WebhookMessageProperties1721298824927"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE messages ADD username text NULL"); - await queryRunner.query("ALTER TABLE messages ADD avatar text NULL"); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE messages ADD username text NULL"); + await queryRunner.query("ALTER TABLE messages ADD avatar text NULL"); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE messages DROP COLUMN username"); - await queryRunner.query("ALTER TABLE messages DROP COLUMN avatar"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE messages DROP COLUMN username"); + await queryRunner.query("ALTER TABLE messages DROP COLUMN avatar"); + } } diff --git a/src/util/migration/postgres/1723347738541-client_status.ts b/src/util/migration/postgres/1723347738541-client_status.ts index c16e049..941e6d7 100644 --- a/src/util/migration/postgres/1723347738541-client_status.ts +++ b/src/util/migration/postgres/1723347738541-client_status.ts @@ -1,13 +1,13 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class client_status1723347738541 implements MigrationInterface { - name = "client_status1723347738541"; + name = "client_status1723347738541"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE sessions ADD client_status text NULL"); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE sessions ADD client_status text NULL"); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE sessions DROP COLUMN client_status"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE sessions DROP COLUMN client_status"); + } } diff --git a/src/util/migration/postgres/1723577874393-discoveryCategoryIcon.ts b/src/util/migration/postgres/1723577874393-discoveryCategoryIcon.ts index 29b4138..8dfde3c 100644 --- a/src/util/migration/postgres/1723577874393-discoveryCategoryIcon.ts +++ b/src/util/migration/postgres/1723577874393-discoveryCategoryIcon.ts @@ -1,13 +1,13 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class DiscoveryCategoryIcon1723577874393 implements MigrationInterface { - name = "DiscoveryCategoryIcon1723577874393"; + name = "DiscoveryCategoryIcon1723577874393"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE categories ADD icon text NULL"); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE categories ADD icon text NULL"); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE categories DROP COLUMN icon"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE categories DROP COLUMN icon"); + } } diff --git a/src/util/migration/postgres/1723644478176-webhookSourceChannel.ts b/src/util/migration/postgres/1723644478176-webhookSourceChannel.ts index 12a726f..00fe413 100644 --- a/src/util/migration/postgres/1723644478176-webhookSourceChannel.ts +++ b/src/util/migration/postgres/1723644478176-webhookSourceChannel.ts @@ -1,17 +1,17 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class WebhookSourceChannel1723644478176 implements MigrationInterface { - name = "WebhookSourceChannel1723644478176"; + name = "WebhookSourceChannel1723644478176"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE webhooks ADD COLUMN source_channel_id VARCHAR(255) NULL DEFAULT NULL"); - await queryRunner.query( - "ALTER TABLE webhooks ADD CONSTRAINT FK_d64f38834fa676f6caa4786ddd6 FOREIGN KEY (source_channel_id) REFERENCES channels (id) ON UPDATE NO ACTION ON DELETE CASCADE", - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE webhooks ADD COLUMN source_channel_id VARCHAR(255) NULL DEFAULT NULL"); + await queryRunner.query( + "ALTER TABLE webhooks ADD CONSTRAINT FK_d64f38834fa676f6caa4786ddd6 FOREIGN KEY (source_channel_id) REFERENCES channels (id) ON UPDATE NO ACTION ON DELETE CASCADE", + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE webhooks DROP CONSTRAINT FK_d64f38834fa676f6caa4786ddd6"); - await queryRunner.query("ALTER TABLE webhooks DROP COLUMN source_channel_id"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE webhooks DROP CONSTRAINT FK_d64f38834fa676f6caa4786ddd6"); + await queryRunner.query("ALTER TABLE webhooks DROP COLUMN source_channel_id"); + } } diff --git a/src/util/migration/postgres/1724477620293-teamMemberRole.ts b/src/util/migration/postgres/1724477620293-teamMemberRole.ts index 9bc92cb..043a1dd 100644 --- a/src/util/migration/postgres/1724477620293-teamMemberRole.ts +++ b/src/util/migration/postgres/1724477620293-teamMemberRole.ts @@ -1,13 +1,13 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class TeamMemberRole1724477620293 implements MigrationInterface { - name = "TeamMemberRole1724477620293"; + name = "TeamMemberRole1724477620293"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE team_members ADD COLUMN role text NOT NULL"); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE team_members ADD COLUMN role text NOT NULL"); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE team_members DROP COLUMN role"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE team_members DROP COLUMN role"); + } } diff --git a/src/util/migration/postgres/1725090962922-applicationProperties.ts b/src/util/migration/postgres/1725090962922-applicationProperties.ts index e396014..03fb172 100644 --- a/src/util/migration/postgres/1725090962922-applicationProperties.ts +++ b/src/util/migration/postgres/1725090962922-applicationProperties.ts @@ -1,15 +1,15 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class ApplicationProperties1725090962922 implements MigrationInterface { - name = "ApplicationProperties1725090962922"; + name = "ApplicationProperties1725090962922"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE applications ADD COLUMN guild_id TEXT NULL DEFAULT NULL"); - await queryRunner.query("ALTER TABLE applications ADD COLUMN custom_install_url TEXT NULL DEFAULT NULL"); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE applications ADD COLUMN guild_id TEXT NULL DEFAULT NULL"); + await queryRunner.query("ALTER TABLE applications ADD COLUMN custom_install_url TEXT NULL DEFAULT NULL"); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query("ALTER TABLE applications DROP COLUMN guild_id"); - await queryRunner.query("ALTER TABLE applications DROP COLUMN custom_install_url"); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE applications DROP COLUMN guild_id"); + await queryRunner.query("ALTER TABLE applications DROP COLUMN custom_install_url"); + } } diff --git a/src/util/migration/postgres/1745625724865-voice.ts b/src/util/migration/postgres/1745625724865-voice.ts index 2310ec2..0e110dd 100644 --- a/src/util/migration/postgres/1745625724865-voice.ts +++ b/src/util/migration/postgres/1745625724865-voice.ts @@ -1,35 +1,35 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class Voice1745625724865 implements MigrationInterface { - name = "Voice1745625724865"; + name = "Voice1745625724865"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "streams" ("id" character varying NOT NULL, "owner_id" character varying NOT NULL, "channel_id" character varying NOT NULL, "endpoint" character varying NOT NULL, CONSTRAINT "PK_40440b6f569ebc02bc71c25c499" PRIMARY KEY ("id"))`, - ); - await queryRunner.query( - `CREATE TABLE "stream_sessions" ("id" character varying NOT NULL, "stream_id" character varying NOT NULL, "user_id" character varying NOT NULL, "token" character varying, "session_id" character varying NOT NULL, "used" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_49bdc3f66394c12478f8371c546" PRIMARY KEY ("id"))`, - ); - await queryRunner.query( - `ALTER TABLE "streams" ADD CONSTRAINT "FK_1b566f9b54d1cda271da53ac82f" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - await queryRunner.query( - `ALTER TABLE "streams" ADD CONSTRAINT "FK_5101f0cded27ff0aae78fc4eed7" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - await queryRunner.query( - `ALTER TABLE "stream_sessions" ADD CONSTRAINT "FK_8b5a028a34dae9ee54af37c9c32" FOREIGN KEY ("stream_id") REFERENCES "streams"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - await queryRunner.query( - `ALTER TABLE "stream_sessions" ADD CONSTRAINT "FK_13ae5c29aff4d0890c54179511a" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "streams" ("id" character varying NOT NULL, "owner_id" character varying NOT NULL, "channel_id" character varying NOT NULL, "endpoint" character varying NOT NULL, CONSTRAINT "PK_40440b6f569ebc02bc71c25c499" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TABLE "stream_sessions" ("id" character varying NOT NULL, "stream_id" character varying NOT NULL, "user_id" character varying NOT NULL, "token" character varying, "session_id" character varying NOT NULL, "used" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_49bdc3f66394c12478f8371c546" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "streams" ADD CONSTRAINT "FK_1b566f9b54d1cda271da53ac82f" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "streams" ADD CONSTRAINT "FK_5101f0cded27ff0aae78fc4eed7" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "stream_sessions" ADD CONSTRAINT "FK_8b5a028a34dae9ee54af37c9c32" FOREIGN KEY ("stream_id") REFERENCES "streams"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "stream_sessions" ADD CONSTRAINT "FK_13ae5c29aff4d0890c54179511a" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "stream_sessions" DROP CONSTRAINT "FK_13ae5c29aff4d0890c54179511a"`); - await queryRunner.query(`ALTER TABLE "stream_sessions" DROP CONSTRAINT "FK_8b5a028a34dae9ee54af37c9c32"`); - await queryRunner.query(`ALTER TABLE "streams" DROP CONSTRAINT "FK_5101f0cded27ff0aae78fc4eed7"`); - await queryRunner.query(`ALTER TABLE "streams" DROP CONSTRAINT "FK_1b566f9b54d1cda271da53ac82f"`); - await queryRunner.query(`DROP TABLE "stream_sessions"`); - await queryRunner.query(`DROP TABLE "streams"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "stream_sessions" DROP CONSTRAINT "FK_13ae5c29aff4d0890c54179511a"`); + await queryRunner.query(`ALTER TABLE "stream_sessions" DROP CONSTRAINT "FK_8b5a028a34dae9ee54af37c9c32"`); + await queryRunner.query(`ALTER TABLE "streams" DROP CONSTRAINT "FK_5101f0cded27ff0aae78fc4eed7"`); + await queryRunner.query(`ALTER TABLE "streams" DROP CONSTRAINT "FK_1b566f9b54d1cda271da53ac82f"`); + await queryRunner.query(`DROP TABLE "stream_sessions"`); + await queryRunner.query(`DROP TABLE "streams"`); + } } diff --git a/src/util/migration/postgres/1752157979333-UserSettingsProtos.ts b/src/util/migration/postgres/1752157979333-UserSettingsProtos.ts index d7cf546..7a24381 100644 --- a/src/util/migration/postgres/1752157979333-UserSettingsProtos.ts +++ b/src/util/migration/postgres/1752157979333-UserSettingsProtos.ts @@ -1,20 +1,20 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class UserSettingsProtos1752157979333 implements MigrationInterface { - name = "UserSettingsProtos1752157979333"; + name = "UserSettingsProtos1752157979333"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP TABLE IF EXISTS "user_settings_protos"`); - await queryRunner.query( - `CREATE TABLE "user_settings_protos" ("user_id" character varying NOT NULL, "userSettings" text, "frecencySettings" text, CONSTRAINT "PK_8ff3d1961a48b693810c9f99853" PRIMARY KEY ("user_id"))`, - ); - await queryRunner.query( - `ALTER TABLE "user_settings_protos" ADD CONSTRAINT "FK_8ff3d1961a48b693810c9f99853" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE IF EXISTS "user_settings_protos"`); + await queryRunner.query( + `CREATE TABLE "user_settings_protos" ("user_id" character varying NOT NULL, "userSettings" text, "frecencySettings" text, CONSTRAINT "PK_8ff3d1961a48b693810c9f99853" PRIMARY KEY ("user_id"))`, + ); + await queryRunner.query( + `ALTER TABLE "user_settings_protos" ADD CONSTRAINT "FK_8ff3d1961a48b693810c9f99853" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "user_settings_protos" DROP CONSTRAINT "FK_8ff3d1961a48b693810c9f99853"`); - await queryRunner.query(`DROP TABLE "user_settings_protos"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_settings_protos" DROP CONSTRAINT "FK_8ff3d1961a48b693810c9f99853"`); + await queryRunner.query(`DROP TABLE "user_settings_protos"`); + } } diff --git a/src/util/migration/postgres/1752321571508-RoleColors.ts b/src/util/migration/postgres/1752321571508-RoleColors.ts index c88476e..0a1cbc9 100644 --- a/src/util/migration/postgres/1752321571508-RoleColors.ts +++ b/src/util/migration/postgres/1752321571508-RoleColors.ts @@ -1,15 +1,15 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class RoleColors1752321571508 implements MigrationInterface { - name = "RoleColors1752321571508"; + name = "RoleColors1752321571508"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "roles" ADD "colors" text`); - await queryRunner.query(`UPDATE "roles" SET "colors" = jsonb_build_object('primary_color', "color") WHERE "colors" IS NULL`); - await queryRunner.query(`ALTER TABLE "roles" ALTER COLUMN "colors" SET NOT NULL`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "roles" ADD "colors" text`); + await queryRunner.query(`UPDATE "roles" SET "colors" = jsonb_build_object('primary_color', "color") WHERE "colors" IS NULL`); + await queryRunner.query(`ALTER TABLE "roles" ALTER COLUMN "colors" SET NOT NULL`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "roles" DROP COLUMN "colors"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "roles" DROP COLUMN "colors"`); + } } diff --git a/src/util/migration/postgres/1752383879533-message_pinned_at.ts b/src/util/migration/postgres/1752383879533-message_pinned_at.ts index b517477..f80b0a0 100644 --- a/src/util/migration/postgres/1752383879533-message_pinned_at.ts +++ b/src/util/migration/postgres/1752383879533-message_pinned_at.ts @@ -1,17 +1,17 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class MessagePinnedAt1752383879533 implements MigrationInterface { - name = "MessagePinnedAt1752383879533"; + name = "MessagePinnedAt1752383879533"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "messages" ADD "pinned_at" TIMESTAMP`); - await queryRunner.query(`UPDATE "messages" SET "pinned_at" = NOW() WHERE "pinned" = true`); - await queryRunner.query(`ALTER TABLE "messages" DROP COLUMN "pinned"`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "messages" ADD "pinned_at" TIMESTAMP`); + await queryRunner.query(`UPDATE "messages" SET "pinned_at" = NOW() WHERE "pinned" = true`); + await queryRunner.query(`ALTER TABLE "messages" DROP COLUMN "pinned"`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "messages" ADD "pinned" boolean`); - await queryRunner.query(`UPDATE "messages" SET "pinned" = true WHERE "pinned_at" IS NOT NULL`); - await queryRunner.query(`ALTER TABLE "messages" DROP COLUMN "pinned_at"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "messages" ADD "pinned" boolean`); + await queryRunner.query(`UPDATE "messages" SET "pinned" = true WHERE "pinned_at" IS NOT NULL`); + await queryRunner.query(`ALTER TABLE "messages" DROP COLUMN "pinned_at"`); + } } diff --git a/src/util/migration/postgres/1753639700904-Automod.ts b/src/util/migration/postgres/1753639700904-Automod.ts index 2df78cc..083edd3 100644 --- a/src/util/migration/postgres/1753639700904-Automod.ts +++ b/src/util/migration/postgres/1753639700904-Automod.ts @@ -1,19 +1,19 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class Automod1753639700904 implements MigrationInterface { - name = "Automod1753639700904"; + name = "Automod1753639700904"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "automod_rules" ("id" character varying NOT NULL, "enabled" boolean NOT NULL, "event_type" integer NOT NULL, "exempt_channels" text NOT NULL, "exempt_roles" text NOT NULL, "guild_id" character varying NOT NULL, "name" character varying NOT NULL, "position" integer NOT NULL, "trigger_type" integer NOT NULL, "trigger_metadata" text, "actions" text NOT NULL, "creator_id" character varying, CONSTRAINT "PK_99789ae863507f5aed9e58d7866" PRIMARY KEY ("id"))`, - ); - await queryRunner.query( - `ALTER TABLE "automod_rules" ADD CONSTRAINT "FK_12d3d60b961393d310429c062b7" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "automod_rules" ("id" character varying NOT NULL, "enabled" boolean NOT NULL, "event_type" integer NOT NULL, "exempt_channels" text NOT NULL, "exempt_roles" text NOT NULL, "guild_id" character varying NOT NULL, "name" character varying NOT NULL, "position" integer NOT NULL, "trigger_type" integer NOT NULL, "trigger_metadata" text, "actions" text NOT NULL, "creator_id" character varying, CONSTRAINT "PK_99789ae863507f5aed9e58d7866" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "automod_rules" ADD CONSTRAINT "FK_12d3d60b961393d310429c062b7" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "automod_rules" DROP CONSTRAINT "FK_12d3d60b961393d310429c062b7"`); - await queryRunner.query(`DROP TABLE "automod_rules"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "automod_rules" DROP CONSTRAINT "FK_12d3d60b961393d310429c062b7"`); + await queryRunner.query(`DROP TABLE "automod_rules"`); + } } diff --git a/src/util/migration/postgres/1758654246197-CloudAttachments.ts b/src/util/migration/postgres/1758654246197-CloudAttachments.ts index 6e462e0..86894f1 100644 --- a/src/util/migration/postgres/1758654246197-CloudAttachments.ts +++ b/src/util/migration/postgres/1758654246197-CloudAttachments.ts @@ -1,23 +1,23 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class CloudAttachments1758654246197 implements MigrationInterface { - name = "CloudAttachments1758654246197"; + name = "CloudAttachments1758654246197"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "cloud_attachments" ("id" character varying NOT NULL, "user_id" character varying, "channel_id" character varying, "upload_filename" character varying NOT NULL, "user_attachment_id" character varying, "user_filename" character varying NOT NULL, "user_file_size" integer, "user_original_content_type" character varying, "user_is_clip" boolean, "size" integer, "height" integer, "width" integer, "content_type" character varying, "userId" character varying, "channelId" character varying, CONSTRAINT "PK_5794827a3ee7c9318612dcb70c8" PRIMARY KEY ("id"))`, - ); - await queryRunner.query( - `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_e6b32df2004e8ad0f488b4a2019" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, - ); - await queryRunner.query( - `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_cab965a18f8ca30293bff3d50a8" FOREIGN KEY ("channelId") REFERENCES "channels"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "cloud_attachments" ("id" character varying NOT NULL, "user_id" character varying, "channel_id" character varying, "upload_filename" character varying NOT NULL, "user_attachment_id" character varying, "user_filename" character varying NOT NULL, "user_file_size" integer, "user_original_content_type" character varying, "user_is_clip" boolean, "size" integer, "height" integer, "width" integer, "content_type" character varying, "userId" character varying, "channelId" character varying, CONSTRAINT "PK_5794827a3ee7c9318612dcb70c8" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_e6b32df2004e8ad0f488b4a2019" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_cab965a18f8ca30293bff3d50a8" FOREIGN KEY ("channelId") REFERENCES "channels"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_cab965a18f8ca30293bff3d50a8"`); - await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_e6b32df2004e8ad0f488b4a2019"`); - await queryRunner.query(`DROP TABLE "cloud_attachments"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_cab965a18f8ca30293bff3d50a8"`); + await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_e6b32df2004e8ad0f488b4a2019"`); + await queryRunner.query(`DROP TABLE "cloud_attachments"`); + } } diff --git a/src/util/migration/postgres/1758654621365-CloudAttachmentsFixIdRefs.ts b/src/util/migration/postgres/1758654621365-CloudAttachmentsFixIdRefs.ts index 6ad6300..dcd1b77 100644 --- a/src/util/migration/postgres/1758654621365-CloudAttachmentsFixIdRefs.ts +++ b/src/util/migration/postgres/1758654621365-CloudAttachmentsFixIdRefs.ts @@ -1,31 +1,31 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class CloudAttachmentsFixIdRefs1758654621365 implements MigrationInterface { - name = "CloudAttachmentsFixIdRefs1758654621365"; + name = "CloudAttachmentsFixIdRefs1758654621365"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_cab965a18f8ca30293bff3d50a8"`); - await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_e6b32df2004e8ad0f488b4a2019"`); - await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP COLUMN "channelId"`); - await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP COLUMN "userId"`); - await queryRunner.query( - `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_8bf8cc8767e48cb482ff644fce6" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, - ); - await queryRunner.query( - `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_998d5fe91008ba5b09e1322104c" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_cab965a18f8ca30293bff3d50a8"`); + await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_e6b32df2004e8ad0f488b4a2019"`); + await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP COLUMN "channelId"`); + await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP COLUMN "userId"`); + await queryRunner.query( + `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_8bf8cc8767e48cb482ff644fce6" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_998d5fe91008ba5b09e1322104c" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_998d5fe91008ba5b09e1322104c"`); - await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_8bf8cc8767e48cb482ff644fce6"`); - await queryRunner.query(`ALTER TABLE "cloud_attachments" ADD "userId" character varying`); - await queryRunner.query(`ALTER TABLE "cloud_attachments" ADD "channelId" character varying`); - await queryRunner.query( - `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_e6b32df2004e8ad0f488b4a2019" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, - ); - await queryRunner.query( - `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_cab965a18f8ca30293bff3d50a8" FOREIGN KEY ("channelId") REFERENCES "channels"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, - ); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_998d5fe91008ba5b09e1322104c"`); + await queryRunner.query(`ALTER TABLE "cloud_attachments" DROP CONSTRAINT "FK_8bf8cc8767e48cb482ff644fce6"`); + await queryRunner.query(`ALTER TABLE "cloud_attachments" ADD "userId" character varying`); + await queryRunner.query(`ALTER TABLE "cloud_attachments" ADD "channelId" character varying`); + await queryRunner.query( + `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_e6b32df2004e8ad0f488b4a2019" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "cloud_attachments" ADD CONSTRAINT "FK_cab965a18f8ca30293bff3d50a8" FOREIGN KEY ("channelId") REFERENCES "channels"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + } } diff --git a/src/util/migration/postgres/1760622755598-ApplicationCommands.ts b/src/util/migration/postgres/1760622755598-ApplicationCommands.ts index e40a3a1..6d4aebc 100644 --- a/src/util/migration/postgres/1760622755598-ApplicationCommands.ts +++ b/src/util/migration/postgres/1760622755598-ApplicationCommands.ts @@ -1,15 +1,15 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class ApplicationCommands1760622755598 implements MigrationInterface { - name = "ApplicationCommands1760622755598"; + name = "ApplicationCommands1760622755598"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "application_commands" ("id" character varying NOT NULL, "type" integer NOT NULL DEFAULT '1', "application_id" character varying NOT NULL, "guild_id" character varying, "name" character varying NOT NULL, "name_localizations" text, "description" character varying NOT NULL, "description_localizations" text, "options" text, "default_member_permissions" character varying, "dm_permission" boolean NOT NULL DEFAULT true, "permissions" text, "nsfw" boolean NOT NULL DEFAULT false, "integration_types" text, "global_popularity_rank" integer NOT NULL DEFAULT '0', "contexts" text, "version" character varying NOT NULL DEFAULT '0', "handler" integer NOT NULL DEFAULT '0', CONSTRAINT "PK_0f73c2f025989c407947e1f75fe" PRIMARY KEY ("id"))`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "application_commands" ("id" character varying NOT NULL, "type" integer NOT NULL DEFAULT '1', "application_id" character varying NOT NULL, "guild_id" character varying, "name" character varying NOT NULL, "name_localizations" text, "description" character varying NOT NULL, "description_localizations" text, "options" text, "default_member_permissions" character varying, "dm_permission" boolean NOT NULL DEFAULT true, "permissions" text, "nsfw" boolean NOT NULL DEFAULT false, "integration_types" text, "global_popularity_rank" integer NOT NULL DEFAULT '0', "contexts" text, "version" character varying NOT NULL DEFAULT '0', "handler" integer NOT NULL DEFAULT '0', CONSTRAINT "PK_0f73c2f025989c407947e1f75fe" PRIMARY KEY ("id"))`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP TABLE "application_commands"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "application_commands"`); + } } diff --git a/src/util/migration/postgres/1760622755599-ReconcileMigrationAttempts.ts b/src/util/migration/postgres/1760622755599-ReconcileMigrationAttempts.ts index 27a2027..fefd547 100644 --- a/src/util/migration/postgres/1760622755599-ReconcileMigrationAttempts.ts +++ b/src/util/migration/postgres/1760622755599-ReconcileMigrationAttempts.ts @@ -1,57 +1,57 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class ReconcileMigrationAttempts1760622755598 implements MigrationInterface { - name = "ReconcileMigrationAttempts1760622755598"; + name = "ReconcileMigrationAttempts1760622755598"; - public async up(queryRunner: QueryRunner): Promise { - // doesnt work because initial setup is syncDb() - //await queryRunner.query(`ALTER TABLE "webhooks" DROP CONSTRAINT "fk_d64f38834fa676f6caa4786ddd6"`); - await queryRunner.query(`ALTER TABLE "webhooks" ALTER COLUMN "source_channel_id" TYPE character varying`); - await queryRunner.query(`ALTER TABLE "messages" ALTER COLUMN "username" TYPE character varying`); - await queryRunner.query(`ALTER TABLE "messages" ALTER COLUMN "avatar" TYPE character varying`); - await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" DROP NOT NULL`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "client_status" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "user_settings" ALTER COLUMN "friend_discovery_flags" DROP DEFAULT`); - await queryRunner.query(`ALTER TABLE "user_settings" ALTER COLUMN "view_nsfw_guilds" DROP DEFAULT`); - await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "flags" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "public_flags" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "purchased_flags" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "team_members" ALTER COLUMN "role" TYPE character varying`); - await queryRunner.query(`ALTER TABLE "team_members" ALTER COLUMN "role" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "applications" ALTER COLUMN "guild_id" TYPE character varying`); - await queryRunner.query(`ALTER TABLE "applications" ALTER COLUMN "custom_install_url" TYPE character varying`); - await queryRunner.query(`ALTER TABLE "categories" ALTER COLUMN "icon" TYPE character varying`); - await queryRunner.query(`ALTER TABLE "user_settings_protos" ALTER COLUMN "userSettings" TYPE character varying`); - await queryRunner.query(`ALTER TABLE "user_settings_protos" ALTER COLUMN "frecencySettings" TYPE character varying`); - await queryRunner.query( - `ALTER TABLE "webhooks" ADD CONSTRAINT "FK_4495b7032a33c6b8b605d030398" FOREIGN KEY ("source_channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - await queryRunner.query( - `ALTER TABLE "applications" ADD CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + // doesnt work because initial setup is syncDb() + //await queryRunner.query(`ALTER TABLE "webhooks" DROP CONSTRAINT "fk_d64f38834fa676f6caa4786ddd6"`); + await queryRunner.query(`ALTER TABLE "webhooks" ALTER COLUMN "source_channel_id" TYPE character varying`); + await queryRunner.query(`ALTER TABLE "messages" ALTER COLUMN "username" TYPE character varying`); + await queryRunner.query(`ALTER TABLE "messages" ALTER COLUMN "avatar" TYPE character varying`); + await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "client_status" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_settings" ALTER COLUMN "friend_discovery_flags" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "user_settings" ALTER COLUMN "view_nsfw_guilds" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "flags" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "public_flags" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "purchased_flags" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "team_members" ALTER COLUMN "role" TYPE character varying`); + await queryRunner.query(`ALTER TABLE "team_members" ALTER COLUMN "role" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "applications" ALTER COLUMN "guild_id" TYPE character varying`); + await queryRunner.query(`ALTER TABLE "applications" ALTER COLUMN "custom_install_url" TYPE character varying`); + await queryRunner.query(`ALTER TABLE "categories" ALTER COLUMN "icon" TYPE character varying`); + await queryRunner.query(`ALTER TABLE "user_settings_protos" ALTER COLUMN "userSettings" TYPE character varying`); + await queryRunner.query(`ALTER TABLE "user_settings_protos" ALTER COLUMN "frecencySettings" TYPE character varying`); + await queryRunner.query( + `ALTER TABLE "webhooks" ADD CONSTRAINT "FK_4495b7032a33c6b8b605d030398" FOREIGN KEY ("source_channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "applications" ADD CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "applications" DROP CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba"`); - await queryRunner.query(`ALTER TABLE "webhooks" DROP CONSTRAINT "FK_4495b7032a33c6b8b605d030398"`); - await queryRunner.query(`ALTER TABLE "user_settings_protos" ALTER COLUMN "frecencySettings" TYPE text`); - await queryRunner.query(`ALTER TABLE "user_settings_protos" ALTER COLUMN "userSettings" TYPE text`); - await queryRunner.query(`ALTER TABLE "categories" ALTER COLUMN "icon" TYPE text`); - await queryRunner.query(`ALTER TABLE "applications" ALTER COLUMN "custom_install_url" TYPE text`); - await queryRunner.query(`ALTER TABLE "applications" ALTER COLUMN "guild_id" TYPE text`); - await queryRunner.query(`ALTER TABLE "team_members" ALTER COLUMN "role" TYPE text`); - await queryRunner.query(`ALTER TABLE "team_members" ALTER COLUMN "role" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "purchased_flags" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "public_flags" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "flags" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "user_settings" ALTER COLUMN "view_nsfw_guilds" SET DEFAULT true`); - await queryRunner.query(`ALTER TABLE "user_settings" ALTER COLUMN "friend_discovery_flags" SET DEFAULT '0'`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "client_status" DROP NOT NULL`); - await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "messages" ALTER COLUMN "avatar" TYPE text`); - await queryRunner.query(`ALTER TABLE "messages" ALTER COLUMN "username" TYPE text`); - await queryRunner.query(`ALTER TABLE "webhooks" ALTER COLUMN "source_channel_id" TYPE character varying(255)`); - await queryRunner.query(`ALTER TABLE "webhooks" ALTER COLUMN "source_channel_id" SET DEFAULT NULL`); - // await queryRunner.query(`ALTER TABLE "webhooks" ADD CONSTRAINT "fk_d64f38834fa676f6caa4786ddd6" FOREIGN KEY ("source_channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "applications" DROP CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba"`); + await queryRunner.query(`ALTER TABLE "webhooks" DROP CONSTRAINT "FK_4495b7032a33c6b8b605d030398"`); + await queryRunner.query(`ALTER TABLE "user_settings_protos" ALTER COLUMN "frecencySettings" TYPE text`); + await queryRunner.query(`ALTER TABLE "user_settings_protos" ALTER COLUMN "userSettings" TYPE text`); + await queryRunner.query(`ALTER TABLE "categories" ALTER COLUMN "icon" TYPE text`); + await queryRunner.query(`ALTER TABLE "applications" ALTER COLUMN "custom_install_url" TYPE text`); + await queryRunner.query(`ALTER TABLE "applications" ALTER COLUMN "guild_id" TYPE text`); + await queryRunner.query(`ALTER TABLE "team_members" ALTER COLUMN "role" TYPE text`); + await queryRunner.query(`ALTER TABLE "team_members" ALTER COLUMN "role" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "purchased_flags" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "public_flags" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "flags" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "user_settings" ALTER COLUMN "view_nsfw_guilds" SET DEFAULT true`); + await queryRunner.query(`ALTER TABLE "user_settings" ALTER COLUMN "friend_discovery_flags" SET DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "client_status" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "channels" ALTER COLUMN "default_thread_rate_limit_per_user" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "messages" ALTER COLUMN "avatar" TYPE text`); + await queryRunner.query(`ALTER TABLE "messages" ALTER COLUMN "username" TYPE text`); + await queryRunner.query(`ALTER TABLE "webhooks" ALTER COLUMN "source_channel_id" TYPE character varying(255)`); + await queryRunner.query(`ALTER TABLE "webhooks" ALTER COLUMN "source_channel_id" SET DEFAULT NULL`); + // await queryRunner.query(`ALTER TABLE "webhooks" ADD CONSTRAINT "fk_d64f38834fa676f6caa4786ddd6" FOREIGN KEY ("source_channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } } diff --git a/src/util/migration/postgres/1760694225225-message_interaction_metadata.ts b/src/util/migration/postgres/1760694225225-message_interaction_metadata.ts index 317361d..2b4afed 100644 --- a/src/util/migration/postgres/1760694225225-message_interaction_metadata.ts +++ b/src/util/migration/postgres/1760694225225-message_interaction_metadata.ts @@ -1,13 +1,13 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class MessageInteractionMetadata1760694225225 implements MigrationInterface { - name = "MessageInteractionMetadata1760694225225"; + name = "MessageInteractionMetadata1760694225225"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "messages" ADD "interaction_metadata" text`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "messages" ADD "interaction_metadata" text`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "messages" DROP COLUMN "interaction_metadata"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "messages" DROP COLUMN "interaction_metadata"`); + } } diff --git a/src/util/migration/postgres/1760961911873-message_reference_fix.ts b/src/util/migration/postgres/1760961911873-message_reference_fix.ts index 1f61ab1..6a29598 100644 --- a/src/util/migration/postgres/1760961911873-message_reference_fix.ts +++ b/src/util/migration/postgres/1760961911873-message_reference_fix.ts @@ -1,19 +1,19 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class MessageReferenceFix1760961911873 implements MigrationInterface { - name = "MessageReferenceFix1760961911873"; + name = "MessageReferenceFix1760961911873"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "messages" DROP CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174"`); - await queryRunner.query( - `ALTER TABLE "messages" ADD CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174" FOREIGN KEY ("message_reference_id") REFERENCES "messages"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "messages" DROP CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174"`); + await queryRunner.query( + `ALTER TABLE "messages" ADD CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174" FOREIGN KEY ("message_reference_id") REFERENCES "messages"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "messages" DROP CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174"`); - await queryRunner.query( - `ALTER TABLE "messages" ADD CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174" FOREIGN KEY ("message_reference_id") REFERENCES "messages"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, - ); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "messages" DROP CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174"`); + await queryRunner.query( + `ALTER TABLE "messages" ADD CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174" FOREIGN KEY ("message_reference_id") REFERENCES "messages"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } } diff --git a/src/util/migration/postgres/1761113394664-delete-bot-users-without-an-application.ts b/src/util/migration/postgres/1761113394664-delete-bot-users-without-an-application.ts index e320992..cf770af 100644 --- a/src/util/migration/postgres/1761113394664-delete-bot-users-without-an-application.ts +++ b/src/util/migration/postgres/1761113394664-delete-bot-users-without-an-application.ts @@ -1,10 +1,10 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class DeleteBotUsersWithoutAnApplication1761113394664 implements MigrationInterface { - name = "DeleteBotUsersWithoutAnApplication1761113394664"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DELETE FROM users WHERE bot = true AND id NOT IN (SELECT bot_user_id FROM applications);`); - } + name = "DeleteBotUsersWithoutAnApplication1761113394664"; + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM users WHERE bot = true AND id NOT IN (SELECT bot_user_id FROM applications);`); + } - public async down(queryRunner: QueryRunner): Promise {} + public async down(queryRunner: QueryRunner): Promise {} } diff --git a/src/util/migration/postgres/1761209437070-application-commands-options-default.ts b/src/util/migration/postgres/1761209437070-application-commands-options-default.ts index 1a1a491..7eba1ee 100644 --- a/src/util/migration/postgres/1761209437070-application-commands-options-default.ts +++ b/src/util/migration/postgres/1761209437070-application-commands-options-default.ts @@ -1,16 +1,16 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class ApplicationCommandsOptionsDefault1761209437070 implements MigrationInterface { - name = "ApplicationCommandsOptionsDefault1761209437070"; + name = "ApplicationCommandsOptionsDefault1761209437070"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`UPDATE "application_commands" SET "options" = '[]' WHERE "options" IS NULL`); - await queryRunner.query(`ALTER TABLE "application_commands" ALTER COLUMN "options" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "application_commands" ALTER COLUMN "options" SET DEFAULT '[]'`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`UPDATE "application_commands" SET "options" = '[]' WHERE "options" IS NULL`); + await queryRunner.query(`ALTER TABLE "application_commands" ALTER COLUMN "options" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "application_commands" ALTER COLUMN "options" SET DEFAULT '[]'`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "application_commands" ALTER COLUMN "options" DROP DEFAULT`); - await queryRunner.query(`ALTER TABLE "application_commands" ALTER COLUMN "options" DROP NOT NULL`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "application_commands" ALTER COLUMN "options" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "application_commands" ALTER COLUMN "options" DROP NOT NULL`); + } } diff --git a/src/util/migration/postgres/1762611552514-fix-gif-stickers-format_type.ts b/src/util/migration/postgres/1762611552514-fix-gif-stickers-format_type.ts index 0e17bf8..c459cb5 100644 --- a/src/util/migration/postgres/1762611552514-fix-gif-stickers-format_type.ts +++ b/src/util/migration/postgres/1762611552514-fix-gif-stickers-format_type.ts @@ -1,12 +1,12 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class FixGifStickersFormatType1762611552514 implements MigrationInterface { - name = "FixGifStickersFormatType1762611552514"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`UPDATE "stickers" SET "format_type" = 4 WHERE "format_type" = 0;`); - } + name = "FixGifStickersFormatType1762611552514"; + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`UPDATE "stickers" SET "format_type" = 4 WHERE "format_type" = 0;`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`UPDATE "stickers" SET "format_type" = 0 WHERE "format_type" = 4;`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`UPDATE "stickers" SET "format_type" = 0 WHERE "format_type" = 4;`); + } } diff --git a/src/util/migration/postgres/1763630755675-default-activities-to-empty-array.ts b/src/util/migration/postgres/1763630755675-default-activities-to-empty-array.ts index cc4b533..ecb9d1e 100644 --- a/src/util/migration/postgres/1763630755675-default-activities-to-empty-array.ts +++ b/src/util/migration/postgres/1763630755675-default-activities-to-empty-array.ts @@ -1,15 +1,15 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class DefaultActivitiesToEmptyArray1763630755675 implements MigrationInterface { - name = "DefaultActivitiesToEmptyArray1763630755675"; + name = "DefaultActivitiesToEmptyArray1763630755675"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "activities" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "activities" SET DEFAULT '[]'`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "activities" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "activities" SET DEFAULT '[]'`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "activities" DROP DEFAULT`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "activities" DROP NOT NULL`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "activities" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "activities" DROP NOT NULL`); + } } diff --git a/src/util/migration/postgres/1765143034407-DontIpBanBanner.ts b/src/util/migration/postgres/1765143034407-DontIpBanBanner.ts index 2207e9c..249c647 100644 --- a/src/util/migration/postgres/1765143034407-DontIpBanBanner.ts +++ b/src/util/migration/postgres/1765143034407-DontIpBanBanner.ts @@ -1,15 +1,15 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class DontIpBanBanner1765143034407 implements MigrationInterface { - name = "DontIpBanBanner1765143034407"; + name = "DontIpBanBanner1765143034407"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "bans" ALTER COLUMN "ip" DROP NOT NULL`); - await queryRunner.query(`UPDATE "bans" SET "ip" = NULL`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "bans" ALTER COLUMN "ip" DROP NOT NULL`); + await queryRunner.query(`UPDATE "bans" SET "ip" = NULL`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`UPDATE "bans" SET "ip" = '0.0.0.0' WHERE "ip" IS NULL`); - await queryRunner.query(`ALTER TABLE "bans" ALTER COLUMN "ip" SET NOT NULL`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`UPDATE "bans" SET "ip" = '0.0.0.0' WHERE "ip" IS NULL`); + await queryRunner.query(`ALTER TABLE "bans" ALTER COLUMN "ip" SET NOT NULL`); + } } diff --git a/src/util/migration/postgres/1765578570423-InstanceBanTable.ts b/src/util/migration/postgres/1765578570423-InstanceBanTable.ts index be8cb9c..205e150 100644 --- a/src/util/migration/postgres/1765578570423-InstanceBanTable.ts +++ b/src/util/migration/postgres/1765578570423-InstanceBanTable.ts @@ -1,19 +1,19 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class InstanceBanTable1765578570423 implements MigrationInterface { - name = "InstanceBanTable1765578570423"; + name = "InstanceBanTable1765578570423"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "instance_bans" ("id" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "reason" character varying NOT NULL, "user_id" character varying, "fingerprint" character varying, "ip_address" character varying, "is_from_other_instance_ban" boolean NOT NULL DEFAULT false, "origin_instance_ban_id" character varying, CONSTRAINT "REL_0b02d18d0d830f160c921192a3" UNIQUE ("origin_instance_ban_id"), CONSTRAINT "PK_3aa6e80a6d325601054892b1340" PRIMARY KEY ("id"))`, - ); - await queryRunner.query( - `ALTER TABLE "instance_bans" ADD CONSTRAINT "FK_0b02d18d0d830f160c921192a30" FOREIGN KEY ("origin_instance_ban_id") REFERENCES "instance_bans"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "instance_bans" ("id" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "reason" character varying NOT NULL, "user_id" character varying, "fingerprint" character varying, "ip_address" character varying, "is_from_other_instance_ban" boolean NOT NULL DEFAULT false, "origin_instance_ban_id" character varying, CONSTRAINT "REL_0b02d18d0d830f160c921192a3" UNIQUE ("origin_instance_ban_id"), CONSTRAINT "PK_3aa6e80a6d325601054892b1340" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "instance_bans" ADD CONSTRAINT "FK_0b02d18d0d830f160c921192a30" FOREIGN KEY ("origin_instance_ban_id") REFERENCES "instance_bans"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "instance_bans" DROP CONSTRAINT "FK_0b02d18d0d830f160c921192a30"`); - await queryRunner.query(`DROP TABLE "instance_bans"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "instance_bans" DROP CONSTRAINT "FK_0b02d18d0d830f160c921192a30"`); + await queryRunner.query(`DROP TABLE "instance_bans"`); + } } diff --git a/src/util/migration/postgres/1765587835846-InstanceBanAllowlist.ts b/src/util/migration/postgres/1765587835846-InstanceBanAllowlist.ts index 8c907aa..9eefea4 100644 --- a/src/util/migration/postgres/1765587835846-InstanceBanAllowlist.ts +++ b/src/util/migration/postgres/1765587835846-InstanceBanAllowlist.ts @@ -1,13 +1,13 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class InstanceBanAllowlist1765587835846 implements MigrationInterface { - name = "InstanceBanAllowlist1765587835846"; + name = "InstanceBanAllowlist1765587835846"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "instance_bans" ADD "is_allowlisted" boolean NOT NULL DEFAULT false`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "instance_bans" ADD "is_allowlisted" boolean NOT NULL DEFAULT false`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "instance_bans" DROP COLUMN "is_allowlisted"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "instance_bans" DROP COLUMN "is_allowlisted"`); + } } diff --git a/src/util/migration/postgres/1765665440000-DropDefaultIPDataKey.ts b/src/util/migration/postgres/1765665440000-DropDefaultIPDataKey.ts index ea887b1..2e58ff7 100644 --- a/src/util/migration/postgres/1765665440000-DropDefaultIPDataKey.ts +++ b/src/util/migration/postgres/1765665440000-DropDefaultIPDataKey.ts @@ -1,17 +1,17 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class DropDefaultIPDataKey1765665440000 implements MigrationInterface { - name = "DropDefaultIPDataKey1765665440000"; + name = "DropDefaultIPDataKey1765665440000"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `UPDATE "config" SET "value" = NULL WHERE "key" = 'security_ipdataApiKey' AND "value" = '"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9"'`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `UPDATE "config" SET "value" = NULL WHERE "key" = 'security_ipdataApiKey' AND "value" = '"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9"'`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `UPDATE "config" SET "value" = '"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9"' WHERE "key" = 'security_ipdataApiKey' AND "value" IS NULL`, - ); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `UPDATE "config" SET "value" = '"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9"' WHERE "key" = 'security_ipdataApiKey' AND "value" IS NULL`, + ); + } } diff --git a/src/util/migration/postgres/1765863159930-ExtendedSessionInfo.ts b/src/util/migration/postgres/1765863159930-ExtendedSessionInfo.ts index c320ed7..d6271cf 100644 --- a/src/util/migration/postgres/1765863159930-ExtendedSessionInfo.ts +++ b/src/util/migration/postgres/1765863159930-ExtendedSessionInfo.ts @@ -1,41 +1,41 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class ExtendedSessionInfo1765863159930 implements MigrationInterface { - name = "ExtendedSessionInfo1765863159930"; + name = "ExtendedSessionInfo1765863159930"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "sessions" DROP CONSTRAINT "PK_3238ef96f18b355b671619111bc"`); - await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "id"`); - await queryRunner.query(`ALTER TABLE "sessions" ADD "is_admin_session" boolean NOT NULL DEFAULT false`); - await queryRunner.query(`ALTER TABLE "sessions" ADD "created_at" TIMESTAMP NOT NULL DEFAULT now()`); - await queryRunner.query(`ALTER TABLE "sessions" ADD "last_seen" TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00'`); - await queryRunner.query(`ALTER TABLE "sessions" ADD "last_seen_ip" character varying NOT NULL DEFAULT '127.0.0.1'`); - await queryRunner.query(`ALTER TABLE "sessions" ADD "last_seen_location" character varying`); - await queryRunner.query(`ALTER TABLE "webhooks" ALTER COLUMN "source_channel_id" DROP DEFAULT`); - await queryRunner.query(`ALTER TABLE "sessions" DROP CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19"`); - await queryRunner.query(`ALTER TABLE "sessions" ADD CONSTRAINT "PK_9340188c93349808f10d1db74a8" PRIMARY KEY ("session_id")`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "user_id" SET NOT NULL`); - await queryRunner.query(`CREATE INDEX "IDX_085d540d9f418cfbdc7bd55bb1" ON "sessions" ("user_id") `); - await queryRunner.query( - `ALTER TABLE "sessions" ADD CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sessions" DROP CONSTRAINT "PK_3238ef96f18b355b671619111bc"`); + await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "id"`); + await queryRunner.query(`ALTER TABLE "sessions" ADD "is_admin_session" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "sessions" ADD "created_at" TIMESTAMP NOT NULL DEFAULT now()`); + await queryRunner.query(`ALTER TABLE "sessions" ADD "last_seen" TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00'`); + await queryRunner.query(`ALTER TABLE "sessions" ADD "last_seen_ip" character varying NOT NULL DEFAULT '127.0.0.1'`); + await queryRunner.query(`ALTER TABLE "sessions" ADD "last_seen_location" character varying`); + await queryRunner.query(`ALTER TABLE "webhooks" ALTER COLUMN "source_channel_id" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "sessions" DROP CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19"`); + await queryRunner.query(`ALTER TABLE "sessions" ADD CONSTRAINT "PK_9340188c93349808f10d1db74a8" PRIMARY KEY ("session_id")`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "user_id" SET NOT NULL`); + await queryRunner.query(`CREATE INDEX "IDX_085d540d9f418cfbdc7bd55bb1" ON "sessions" ("user_id") `); + await queryRunner.query( + `ALTER TABLE "sessions" ADD CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "sessions" DROP CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19"`); - await queryRunner.query(`ALTER TABLE "webhooks" DROP CONSTRAINT "FK_4495b7032a33c6b8b605d030398"`); - await queryRunner.query(`DROP INDEX "public"."IDX_085d540d9f418cfbdc7bd55bb1"`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "user_id" DROP NOT NULL`); - await queryRunner.query(`ALTER TABLE "sessions" DROP CONSTRAINT "PK_9340188c93349808f10d1db74a8"`); - await queryRunner.query( - `ALTER TABLE "sessions" ADD CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, - ); - await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "last_seen_location"`); - await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "last_seen_ip"`); - await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "last_seen"`); - await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "created_at"`); - await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "is_admin_session"`); - await queryRunner.query(`ALTER TABLE "sessions" ADD "id" character varying NOT NULL`); - await queryRunner.query(`ALTER TABLE "sessions" ADD CONSTRAINT "PK_3238ef96f18b355b671619111bc" PRIMARY KEY ("id")`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sessions" DROP CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19"`); + await queryRunner.query(`ALTER TABLE "webhooks" DROP CONSTRAINT "FK_4495b7032a33c6b8b605d030398"`); + await queryRunner.query(`DROP INDEX "public"."IDX_085d540d9f418cfbdc7bd55bb1"`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "user_id" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "sessions" DROP CONSTRAINT "PK_9340188c93349808f10d1db74a8"`); + await queryRunner.query( + `ALTER TABLE "sessions" ADD CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "last_seen_location"`); + await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "last_seen_ip"`); + await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "last_seen"`); + await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "created_at"`); + await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "is_admin_session"`); + await queryRunner.query(`ALTER TABLE "sessions" ADD "id" character varying NOT NULL`); + await queryRunner.query(`ALTER TABLE "sessions" ADD CONSTRAINT "PK_3238ef96f18b355b671619111bc" PRIMARY KEY ("id")`); + } } diff --git a/src/util/migration/postgres/1765932247127-UpdateSessionInfo.ts b/src/util/migration/postgres/1765932247127-UpdateSessionInfo.ts index 3da3498..fb8a7e5 100644 --- a/src/util/migration/postgres/1765932247127-UpdateSessionInfo.ts +++ b/src/util/migration/postgres/1765932247127-UpdateSessionInfo.ts @@ -1,23 +1,23 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class UpdateSessionInfo1765932247127 implements MigrationInterface { - name = "UpdateSessionInfo1765932247127"; + name = "UpdateSessionInfo1765932247127"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "sessions" ADD "last_seen_location_info" text`); - await queryRunner.query(`ALTER TABLE "sessions" ADD "session_nickname" character varying`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen" DROP NOT NULL`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen" DROP DEFAULT`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen_ip" DROP NOT NULL`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen_ip" DROP DEFAULT`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sessions" ADD "last_seen_location_info" text`); + await queryRunner.query(`ALTER TABLE "sessions" ADD "session_nickname" character varying`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen_ip" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen_ip" DROP DEFAULT`); + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen_ip" SET DEFAULT '127.0.0.1'`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen_ip" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen" SET DEFAULT '1970-01-01 00:00:00'`); - await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen" SET NOT NULL`); - await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "session_nickname"`); - await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "last_seen_location_info"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen_ip" SET DEFAULT '127.0.0.1'`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen_ip" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen" SET DEFAULT '1970-01-01 00:00:00'`); + await queryRunner.query(`ALTER TABLE "sessions" ALTER COLUMN "last_seen" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "session_nickname"`); + await queryRunner.query(`ALTER TABLE "sessions" DROP COLUMN "last_seen_location_info"`); + } } diff --git a/src/util/util/ApiError.ts b/src/util/util/ApiError.ts index 161c209..08bbc14 100644 --- a/src/util/util/ApiError.ts +++ b/src/util/util/ApiError.ts @@ -17,29 +17,29 @@ */ export class ApiError extends Error { - constructor( - readonly message: string, - public readonly code: number, - public readonly httpStatus: number = 400, - public readonly defaultParams?: string[], - ) { - super(message); - } + constructor( + readonly message: string, + public readonly code: number, + public readonly httpStatus: number = 400, + public readonly defaultParams?: string[], + ) { + super(message); + } - withDefaultParams(): ApiError { - if (this.defaultParams) return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus); - return this; - } + withDefaultParams(): ApiError { + if (this.defaultParams) return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus); + return this; + } - withParams(...params: (string | number)[]): ApiError { - return new ApiError(applyParamsToString(this.message, params), this.code, this.httpStatus); - } + withParams(...params: (string | number)[]): ApiError { + return new ApiError(applyParamsToString(this.message, params), this.code, this.httpStatus); + } } export function applyParamsToString(s: string, params: (string | number)[]): string { - let newString = s; - params.forEach((a) => { - newString = newString.replace("{}", "" + a); - }); - return newString; + let newString = s; + params.forEach((a) => { + newString = newString.replace("{}", "" + a); + }); + return newString; } diff --git a/src/util/util/Application.ts b/src/util/util/Application.ts index 35cf6a3..64c8777 100644 --- a/src/util/util/Application.ts +++ b/src/util/util/Application.ts @@ -2,24 +2,24 @@ import { Application, User } from "../entities"; export async function createAppBotUser(app: Application, req: Request) { - const user = await User.register({ - username: app.name, - password: undefined, - id: app.id, - req, - bot: true, - }); + const user = await User.register({ + username: app.name, + password: undefined, + id: app.id, + req, + bot: true, + }); - user.id = app.id; - user.premium_since = new Date(); - user.bot = true; + user.id = app.id; + user.premium_since = new Date(); + user.bot = true; - await user.save(); + await user.save(); - // flags is NaN here? - app.assign({ bot: user, flags: app.flags || 0 }); + // flags is NaN here? + app.assign({ bot: user, flags: app.flags || 0 }); - await app.save(); + await app.save(); - return user; + return user; } diff --git a/src/util/util/AutoUpdate.ts b/src/util/util/AutoUpdate.ts index 19f1116..d9fe93a 100644 --- a/src/util/util/AutoUpdate.ts +++ b/src/util/util/AutoUpdate.ts @@ -21,70 +21,70 @@ import path from "path"; const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, + input: process.stdin, + output: process.stdout, }); export function enableAutoUpdate(opts: { checkInterval: number | boolean; packageJsonLink: string; path: string; downloadUrl: string; downloadType?: "zip" }) { - if (!opts.checkInterval) return; - const interval = 1000 * 60 * 60 * 24; - if (typeof opts.checkInterval === "number") opts.checkInterval = 1000 * interval; + if (!opts.checkInterval) return; + const interval = 1000 * 60 * 60 * 24; + if (typeof opts.checkInterval === "number") opts.checkInterval = 1000 * interval; - const i = setInterval(async () => { - const currentVersion = await getCurrentVersion(opts.path); - const latestVersion = await getLatestVersion(opts.packageJsonLink); - if (currentVersion !== latestVersion) { - clearInterval(i); - console.log(`[Auto Update] Current version (${currentVersion}) is out of date, updating ...`); - await download(opts.downloadUrl, opts.path); - } - }, interval); - setImmediate(async () => { - const currentVersion = await getCurrentVersion(opts.path); - const latestVersion = await getLatestVersion(opts.packageJsonLink); - if (currentVersion !== latestVersion) { - rl.question(`[Auto Update] Current version (${currentVersion}) is out of date, would you like to update? (Y/n)`, (answer) => { - if (answer === "" || answer.toLowerCase() === "y") { - console.log(`[Auto update] updating ...`); - download(opts.downloadUrl, opts.path); - } else { - console.log(`[Auto update] aborted`); - } - }); - } - }); + const i = setInterval(async () => { + const currentVersion = await getCurrentVersion(opts.path); + const latestVersion = await getLatestVersion(opts.packageJsonLink); + if (currentVersion !== latestVersion) { + clearInterval(i); + console.log(`[Auto Update] Current version (${currentVersion}) is out of date, updating ...`); + await download(opts.downloadUrl, opts.path); + } + }, interval); + setImmediate(async () => { + const currentVersion = await getCurrentVersion(opts.path); + const latestVersion = await getLatestVersion(opts.packageJsonLink); + if (currentVersion !== latestVersion) { + rl.question(`[Auto Update] Current version (${currentVersion}) is out of date, would you like to update? (Y/n)`, (answer) => { + if (answer === "" || answer.toLowerCase() === "y") { + console.log(`[Auto update] updating ...`); + download(opts.downloadUrl, opts.path); + } else { + console.log(`[Auto update] aborted`); + } + }); + } + }); } async function download(url: string, dir: string) { - try { - // TODO: use file stream instead of buffer (to prevent crash because of high memory usage for big files) - // TODO check file hash - const response = await fetch(url); - const buffer = await response.bytes(); - const tempDir = await fs.mkdtemp("spacebar"); - await fs.writeFile(path.join(tempDir, "Spacebar.zip"), buffer); - } catch (error) { - console.error(`[Auto Update] download failed`, error); - } + try { + // TODO: use file stream instead of buffer (to prevent crash because of high memory usage for big files) + // TODO check file hash + const response = await fetch(url); + const buffer = await response.bytes(); + const tempDir = await fs.mkdtemp("spacebar"); + await fs.writeFile(path.join(tempDir, "Spacebar.zip"), buffer); + } catch (error) { + console.error(`[Auto Update] download failed`, error); + } } async function getCurrentVersion(dir: string) { - try { - const content = await fs.readFile(path.join(dir, "package.json"), { - encoding: "utf8", - }); - return JSON.parse(content).version; - } catch (error) { - throw new Error("[Auto update] couldn't get current version in " + dir); - } + try { + const content = await fs.readFile(path.join(dir, "package.json"), { + encoding: "utf8", + }); + return JSON.parse(content).version; + } catch (error) { + throw new Error("[Auto update] couldn't get current version in " + dir); + } } async function getLatestVersion(url: string) { - try { - const response = await fetch(url); - const content = (await response.json()) as { version: string }; - return content.version; - } catch (error) { - throw new Error("[Auto update] check failed for " + url); - } + try { + const response = await fetch(url); + const content = (await response.json()) as { version: string }; + return content.version; + } catch (error) { + throw new Error("[Auto update] check failed for " + url); + } } diff --git a/src/util/util/BitField.ts b/src/util/util/BitField.ts index 85de8fb..8a7dc94 100644 --- a/src/util/util/BitField.ts +++ b/src/util/util/BitField.ts @@ -10,149 +10,149 @@ * Data structure that makes it easy to interact with a bitfield. */ export class BitField { - public bitfield: bigint = BigInt(0); + public bitfield: bigint = BigInt(0); - public static FLAGS: Record = {}; + public static FLAGS: Record = {}; - constructor(bits: BitFieldResolvable = 0) { - this.bitfield = BitField.resolve.call(this, bits); - } + constructor(bits: BitFieldResolvable = 0) { + this.bitfield = BitField.resolve.call(this, bits); + } - /** - * Checks whether the bitfield has a bit, or any of multiple bits. - */ - any(bit: BitFieldResolvable): boolean { - return (this.bitfield & BitField.resolve.call(this, bit)) !== BigInt(0); - } + /** + * Checks whether the bitfield has a bit, or any of multiple bits. + */ + any(bit: BitFieldResolvable): boolean { + return (this.bitfield & BitField.resolve.call(this, bit)) !== BigInt(0); + } - /** - * Checks if this bitfield equals another - */ - equals(bit: BitFieldResolvable): boolean { - return this.bitfield === BitField.resolve.call(this, bit); - } + /** + * Checks if this bitfield equals another + */ + equals(bit: BitFieldResolvable): boolean { + return this.bitfield === BitField.resolve.call(this, bit); + } - /** - * Checks whether the bitfield has a bit, or multiple bits. - */ - has(bit: BitFieldResolvable): boolean { - if (Array.isArray(bit)) return bit.every((p) => this.has(p)); - const BIT = BitField.resolve.call(this, bit); - return (this.bitfield & BIT) === BIT; - } + /** + * Checks whether the bitfield has a bit, or multiple bits. + */ + has(bit: BitFieldResolvable): boolean { + if (Array.isArray(bit)) return bit.every((p) => this.has(p)); + const BIT = BitField.resolve.call(this, bit); + return (this.bitfield & BIT) === BIT; + } - /** - * Gets all given bits that are missing from the bitfield. - */ - missing(bits: BitFieldResolvable) { - if (!Array.isArray(bits)) bits = new BitField(bits).toArray(); - return bits.filter((p) => !this.has(p)); - } + /** + * Gets all given bits that are missing from the bitfield. + */ + missing(bits: BitFieldResolvable) { + if (!Array.isArray(bits)) bits = new BitField(bits).toArray(); + return bits.filter((p) => !this.has(p)); + } - /** - * Freezes these bits, making them immutable. - */ - freeze(): Readonly { - return Object.freeze(this); - } + /** + * Freezes these bits, making them immutable. + */ + freeze(): Readonly { + return Object.freeze(this); + } - /** - * Adds bits to these ones. - * @param {...BitFieldResolvable} [bits] Bits to add - * @returns {BitField} These bits or new BitField if the instance is frozen. - */ - add(...bits: BitFieldResolvable[]): BitField { - let total = BigInt(0); - for (const bit of bits) { - total |= BitField.resolve.call(this, bit); - } - if (Object.isFrozen(this)) return new BitField(this.bitfield | total); - this.bitfield |= total; - return this; - } + /** + * Adds bits to these ones. + * @param {...BitFieldResolvable} [bits] Bits to add + * @returns {BitField} These bits or new BitField if the instance is frozen. + */ + add(...bits: BitFieldResolvable[]): BitField { + let total = BigInt(0); + for (const bit of bits) { + total |= BitField.resolve.call(this, bit); + } + if (Object.isFrozen(this)) return new BitField(this.bitfield | total); + this.bitfield |= total; + return this; + } - /** - * Removes bits from these. - * @param {...BitFieldResolvable} [bits] Bits to remove - */ - remove(...bits: BitFieldResolvable[]) { - let total = BigInt(0); - for (const bit of bits) { - total |= BitField.resolve.call(this, bit); - } - if (Object.isFrozen(this)) return new BitField(this.bitfield & ~total); - this.bitfield &= ~total; - return this; - } + /** + * Removes bits from these. + * @param {...BitFieldResolvable} [bits] Bits to remove + */ + remove(...bits: BitFieldResolvable[]) { + let total = BigInt(0); + for (const bit of bits) { + total |= BitField.resolve.call(this, bit); + } + if (Object.isFrozen(this)) return new BitField(this.bitfield & ~total); + this.bitfield &= ~total; + return this; + } - /** - * Gets an object mapping field names to a {@link boolean} indicating whether the - * bit is available. - * @param {...*} hasParams Additional parameters for the has method, if any - */ - serialize() { - const serialized: Record = {}; - for (const [flag, bit] of Object.entries(BitField.FLAGS)) serialized[flag] = this.has(bit); - return serialized; - } + /** + * Gets an object mapping field names to a {@link boolean} indicating whether the + * bit is available. + * @param {...*} hasParams Additional parameters for the has method, if any + */ + serialize() { + const serialized: Record = {}; + for (const [flag, bit] of Object.entries(BitField.FLAGS)) serialized[flag] = this.has(bit); + return serialized; + } - /** - * Gets an {@link Array} of bitfield names based on the bits available. - */ - toArray(): string[] { - return Object.keys(BitField.FLAGS).filter((bit) => this.has(bit)); - } + /** + * Gets an {@link Array} of bitfield names based on the bits available. + */ + toArray(): string[] { + return Object.keys(BitField.FLAGS).filter((bit) => this.has(bit)); + } - toJSON() { - return this.bitfield; - } + toJSON() { + return this.bitfield; + } - valueOf() { - return this.bitfield; - } + valueOf() { + return this.bitfield; + } - *[Symbol.iterator]() { - yield* this.toArray(); - } + *[Symbol.iterator]() { + yield* this.toArray(); + } - /** - * Data that can be resolved to give a bitfield. This can be: - * * A bit number (this can be a number literal or a value taken from {@link BitField.FLAGS}) - * * An instance of BitField - * * An Array of BitFieldResolvable - * @typedef {number|BitField|BitFieldResolvable[]} BitFieldResolvable - */ + /** + * Data that can be resolved to give a bitfield. This can be: + * * A bit number (this can be a number literal or a value taken from {@link BitField.FLAGS}) + * * An instance of BitField + * * An Array of BitFieldResolvable + * @typedef {number|BitField|BitFieldResolvable[]} BitFieldResolvable + */ - /** - * Resolves bitfields to their numeric form. - * @param {BitFieldResolvable} [bit=0] - bit(s) to resolve - * @returns {number} - */ - static resolve(bit: BitFieldResolvable = BigInt(0)): bigint { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const FLAGS = this.FLAGS || this.constructor?.FLAGS; + /** + * Resolves bitfields to their numeric form. + * @param {BitFieldResolvable} [bit=0] - bit(s) to resolve + * @returns {number} + */ + static resolve(bit: BitFieldResolvable = BigInt(0)): bigint { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const FLAGS = this.FLAGS || this.constructor?.FLAGS; - if (typeof bit === "string") { - if (typeof FLAGS[bit] !== "undefined") return FLAGS[bit]; - else bit = BigInt(bit); - } + if (typeof bit === "string") { + if (typeof FLAGS[bit] !== "undefined") return FLAGS[bit]; + else bit = BigInt(bit); + } - if ((typeof bit === "number" || typeof bit === "bigint") && bit >= BigInt(0)) return BigInt(bit); + if ((typeof bit === "number" || typeof bit === "bigint") && bit >= BigInt(0)) return BigInt(bit); - if (bit instanceof BitField) return bit.bitfield; + if (bit instanceof BitField) return bit.bitfield; - if (Array.isArray(bit)) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const resolve = this.constructor?.resolve || this.resolve; - return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), BigInt(0)); - } + if (Array.isArray(bit)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const resolve = this.constructor?.resolve || this.resolve; + return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), BigInt(0)); + } - throw new RangeError("BITFIELD_INVALID: " + bit); - } + throw new RangeError("BITFIELD_INVALID: " + bit); + } } export function BitFlag(x: bigint | number) { - return BigInt(1) << BigInt(x); + return BigInt(1) << BigInt(x); } diff --git a/src/util/util/Config.ts b/src/util/util/Config.ts index e4b5f36..1ca455f 100644 --- a/src/util/util/Config.ts +++ b/src/util/util/Config.ts @@ -33,165 +33,165 @@ // Config keys are separated with _ export class Config { - public static async init(force: boolean = false) { - if (config && !force) return config; - console.log("[Config] Loading configuration..."); - if (!process.env.CONFIG_PATH) { - pairs = await validateConfig(); - config = pairsToConfig(pairs); - } else { - console.log(`[Config] Using CONFIG_PATH rather than database`); - if (existsSync(process.env.CONFIG_PATH)) { - const file = JSON.parse((await fs.readFile(process.env.CONFIG_PATH)).toString()); - config = file; - } else config = new ConfigValue(); - pairs = generatePairs(config); - } + public static async init(force: boolean = false) { + if (config && !force) return config; + console.log("[Config] Loading configuration..."); + if (!process.env.CONFIG_PATH) { + pairs = await validateConfig(); + config = pairsToConfig(pairs); + } else { + console.log(`[Config] Using CONFIG_PATH rather than database`); + if (existsSync(process.env.CONFIG_PATH)) { + const file = JSON.parse((await fs.readFile(process.env.CONFIG_PATH)).toString()); + config = file; + } else config = new ConfigValue(); + pairs = generatePairs(config); + } - // If a config doesn't exist, create it. - if (Object.keys(config).length == 0) config = new ConfigValue(); + // If a config doesn't exist, create it. + if (Object.keys(config).length == 0) config = new ConfigValue(); - config = OrmUtils.mergeDeep({}, { ...new ConfigValue() }, config); + config = OrmUtils.mergeDeep({}, { ...new ConfigValue() }, config); - await this.set(config); - validateFinalConfig(config); - return config; - } - public static get() { - if (!config) { - // If we haven't initialised the config yet, return default config. - // Typeorm instantiates each entity once when initialising database, - // which means when we use config values as default values in entity classes, - // the config isn't initialised yet and would throw an error about the config being undefined. + await this.set(config); + validateFinalConfig(config); + return config; + } + public static get() { + if (!config) { + // If we haven't initialised the config yet, return default config. + // Typeorm instantiates each entity once when initialising database, + // which means when we use config values as default values in entity classes, + // the config isn't initialised yet and would throw an error about the config being undefined. - return new ConfigValue(); - } + return new ConfigValue(); + } - return config; - } - public static set(val: Partial) { - if (!config || !val) return; - config = OrmUtils.mergeDeep(config); + return config; + } + public static set(val: Partial) { + if (!config || !val) return; + config = OrmUtils.mergeDeep(config); - return applyConfig(config); - } + return applyConfig(config); + } } // TODO: better types const generatePairs = (obj: object | null, key = ""): ConfigEntity[] => { - if (typeof obj == "object" && obj != null) { - return Object.keys(obj) - .map((k) => - // eslint-disable-next-line @typescript-eslint/no-explicit-any - generatePairs((obj as any)[k], key ? `${key}_${k}` : k), - ) - .flat(); - } + if (typeof obj == "object" && obj != null) { + return Object.keys(obj) + .map((k) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + generatePairs((obj as any)[k], key ? `${key}_${k}` : k), + ) + .flat(); + } - const ret = new ConfigEntity(); - ret.key = key; - ret.value = obj; - return [ret]; + const ret = new ConfigEntity(); + ret.key = key; + ret.value = obj; + return [ret]; }; async function applyConfig(val: ConfigValue) { - if (process.env.CONFIG_PATH) - if (!process.env.CONFIG_READONLY) await fs.writeFile(overridePath, JSON.stringify(val, null, 4)); - else console.log("[WARNING] JSON config file in use, and writing is disabled! Programmatic config changes will not be persisted, and your config will not get updated!"); - else { - const pairs = generatePairs(val); - // keys are sorted to try to influence database order... - await Promise.all(pairs.sort((x, y) => (x.key > y.key ? 1 : -1)).map((pair) => pair.save())); - } - return val; + if (process.env.CONFIG_PATH) + if (!process.env.CONFIG_READONLY) await fs.writeFile(overridePath, JSON.stringify(val, null, 4)); + else console.log("[WARNING] JSON config file in use, and writing is disabled! Programmatic config changes will not be persisted, and your config will not get updated!"); + else { + const pairs = generatePairs(val); + // keys are sorted to try to influence database order... + await Promise.all(pairs.sort((x, y) => (x.key > y.key ? 1 : -1)).map((pair) => pair.save())); + } + return val; } function pairsToConfig(pairs: ConfigEntity[]) { - // TODO: typings - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const value: any = {}; + // TODO: typings + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const value: any = {}; - pairs.forEach((p) => { - const keys = p.key.split("_"); - let obj = value; - let prev = ""; - let prevObj = obj; - let i = 0; + pairs.forEach((p) => { + const keys = p.key.split("_"); + let obj = value; + let prev = ""; + let prevObj = obj; + let i = 0; - for (const key of keys) { - if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = []; - if (i++ === keys.length - 1) obj[key] = p.value; - else if (!obj[key]) obj[key] = {}; + for (const key of keys) { + if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = []; + if (i++ === keys.length - 1) obj[key] = p.value; + else if (!obj[key]) obj[key] = {}; - prev = key; - prevObj = obj; - obj = obj[key]; - } - }); + prev = key; + prevObj = obj; + obj = obj[key]; + } + }); - return value as ConfigValue; + return value as ConfigValue; } const validateConfig = async () => { - let hasErrored = false; - const totalStartTime = new Date(); - const config = await ConfigEntity.find({ select: { key: true } }); + let hasErrored = false; + const totalStartTime = new Date(); + const config = await ConfigEntity.find({ select: { key: true } }); - for (const row in config) { - // extension methods... - if (typeof config[row] === "function") continue; + for (const row in config) { + // extension methods... + if (typeof config[row] === "function") continue; - try { - const found = await ConfigEntity.findOne({ - where: { key: config[row].key }, - }); - if (!found) continue; - config[row] = found; - } catch (e) { - console.error(`Config key '${config[row].key}' has invalid JSON value : ${(e as Error)?.message}`); - hasErrored = true; - } - } + try { + const found = await ConfigEntity.findOne({ + where: { key: config[row].key }, + }); + if (!found) continue; + config[row] = found; + } catch (e) { + console.error(`Config key '${config[row].key}' has invalid JSON value : ${(e as Error)?.message}`); + hasErrored = true; + } + } - console.log("[Config] Total config load time:", new Date().getTime() - totalStartTime.getTime(), "ms"); + console.log("[Config] Total config load time:", new Date().getTime() - totalStartTime.getTime(), "ms"); - if (hasErrored) { - console.error("[Config] Your config has invalid values. Fix them first https://docs.spacebar.chat/setup/server/configuration"); - process.exit(1); - } + if (hasErrored) { + console.error("[Config] Your config has invalid values. Fix them first https://docs.spacebar.chat/setup/server/configuration"); + process.exit(1); + } - return config; + return config; }; function validateFinalConfig(config: ConfigValue) { - let hasErrors = false; - function assertConfig(path: string, condition: (val: JsonValue) => boolean, recommendedValue: string) { - // _ to separate keys - const keys = path.split("_"); - let obj: never = config as never; + let hasErrors = false; + function assertConfig(path: string, condition: (val: JsonValue) => boolean, recommendedValue: string) { + // _ to separate keys + const keys = path.split("_"); + let obj: never = config as never; - for (const key of keys) { - if (obj == null || !(key in obj)) { - console.warn(`[Config] Missing config value for '${path}'. Recommended value: ${recommendedValue}`); - return; - } - obj = obj[key]; - } + for (const key of keys) { + if (obj == null || !(key in obj)) { + console.warn(`[Config] Missing config value for '${path}'. Recommended value: ${recommendedValue}`); + return; + } + obj = obj[key]; + } - if (!condition(obj)) { - console.warn(`[Config] Invalid config value for '${path}': ${obj}. Recommended value: ${recommendedValue}`); - hasErrors = true; - } - } + if (!condition(obj)) { + console.warn(`[Config] Invalid config value for '${path}': ${obj}. Recommended value: ${recommendedValue}`); + hasErrors = true; + } + } - assertConfig("api_endpointPublic", (v) => v != null, 'A valid public API endpoint URL, ex. "http://localhost:3001/api/v9"'); - assertConfig("cdn_endpointPublic", (v) => v != null, 'A valid public CDN endpoint URL, ex. "http://localhost:3003/"'); - assertConfig("cdn_endpointPrivate", (v) => v != null, 'A valid private CDN endpoint URL, ex. "http://localhost:3003/" - must be routable from the API server!'); - assertConfig("gateway_endpointPublic", (v) => v != null, 'A valid public gateway endpoint URL, ex. "ws://localhost:3002/"'); + assertConfig("api_endpointPublic", (v) => v != null, 'A valid public API endpoint URL, ex. "http://localhost:3001/api/v9"'); + assertConfig("cdn_endpointPublic", (v) => v != null, 'A valid public CDN endpoint URL, ex. "http://localhost:3003/"'); + assertConfig("cdn_endpointPrivate", (v) => v != null, 'A valid private CDN endpoint URL, ex. "http://localhost:3003/" - must be routable from the API server!'); + assertConfig("gateway_endpointPublic", (v) => v != null, 'A valid public gateway endpoint URL, ex. "ws://localhost:3002/"'); - if (hasErrors) { - console.error("[Config] Your config has invalid values. Fix them first https://docs.spacebar.chat/setup/server/configuration"); - console.error("[Config] Hint: if you're just testing with bundle (`npm run start`), you can set all endpoint URLs to [proto]://localhost:3001"); - process.exit(1); - } else console.log("[Config] Configuration validated successfully."); + if (hasErrors) { + console.error("[Config] Your config has invalid values. Fix them first https://docs.spacebar.chat/setup/server/configuration"); + console.error("[Config] Hint: if you're just testing with bundle (`npm run start`), you can set all endpoint URLs to [proto]://localhost:3001"); + process.exit(1); + } else console.log("[Config] Configuration validated successfully."); } diff --git a/src/util/util/Constants.ts b/src/util/util/Constants.ts index 2c8c587..5bfc1f7 100644 --- a/src/util/util/Constants.ts +++ b/src/util/util/Constants.ts @@ -19,12 +19,12 @@ import { ApiError } from "./ApiError"; export const WSCodes = { - 1000: "WS_CLOSE_REQUESTED", - 4004: "TOKEN_INVALID", - 4010: "SHARDING_INVALID", - 4011: "SHARDING_REQUIRED", - 4013: "INVALID_INTENTS", - 4014: "DISALLOWED_INTENTS", + 1000: "WS_CLOSE_REQUESTED", + 4004: "TOKEN_INVALID", + 4010: "SHARDING_INVALID", + 4011: "SHARDING_REQUIRED", + 4013: "INVALID_INTENTS", + 4014: "DISALLOWED_INTENTS", }; /** @@ -41,97 +41,97 @@ * @typedef {number} Status */ export const WsStatus = { - READY: 0, - CONNECTING: 1, - RECONNECTING: 2, - IDLE: 3, - NEARLY: 4, - DISCONNECTED: 5, - WAITING_FOR_GUILDS: 6, - IDENTIFYING: 7, - RESUMING: 8, + READY: 0, + CONNECTING: 1, + RECONNECTING: 2, + IDLE: 3, + NEARLY: 4, + DISCONNECTED: 5, + WAITING_FOR_GUILDS: 6, + IDENTIFYING: 7, + RESUMING: 8, }; export const OPCodes = { - DISPATCH: 0, - HEARTBEAT: 1, - IDENTIFY: 2, - STATUS_UPDATE: 3, - VOICE_STATE_UPDATE: 4, - VOICE_GUILD_PING: 5, - RESUME: 6, - RECONNECT: 7, - REQUEST_GUILD_MEMBERS: 8, - INVALID_SESSION: 9, - HELLO: 10, - HEARTBEAT_ACK: 11, + DISPATCH: 0, + HEARTBEAT: 1, + IDENTIFY: 2, + STATUS_UPDATE: 3, + VOICE_STATE_UPDATE: 4, + VOICE_GUILD_PING: 5, + RESUME: 6, + RECONNECT: 7, + REQUEST_GUILD_MEMBERS: 8, + INVALID_SESSION: 9, + HELLO: 10, + HEARTBEAT_ACK: 11, }; export const Events = { - RATE_LIMIT: "rateLimit", - CLIENT_READY: "ready", - GUILD_CREATE: "guildCreate", - GUILD_DELETE: "guildDelete", - GUILD_UPDATE: "guildUpdate", - GUILD_UNAVAILABLE: "guildUnavailable", - GUILD_AVAILABLE: "guildAvailable", - GUILD_MEMBER_ADD: "guildMemberAdd", - GUILD_MEMBER_REMOVE: "guildMemberRemove", - GUILD_MEMBER_UPDATE: "guildMemberUpdate", - GUILD_MEMBER_AVAILABLE: "guildMemberAvailable", - GUILD_MEMBER_SPEAKING: "guildMemberSpeaking", - GUILD_MEMBERS_CHUNK: "guildMembersChunk", - GUILD_INTEGRATIONS_UPDATE: "guildIntegrationsUpdate", - GUILD_ROLE_CREATE: "roleCreate", - GUILD_ROLE_DELETE: "roleDelete", - INVITE_CREATE: "inviteCreate", - INVITE_DELETE: "inviteDelete", - GUILD_ROLE_UPDATE: "roleUpdate", - GUILD_EMOJI_CREATE: "emojiCreate", - GUILD_EMOJI_DELETE: "emojiDelete", - GUILD_EMOJI_UPDATE: "emojiUpdate", - GUILD_BAN_ADD: "guildBanAdd", - GUILD_BAN_REMOVE: "guildBanRemove", - CHANNEL_CREATE: "channelCreate", - CHANNEL_DELETE: "channelDelete", - CHANNEL_UPDATE: "channelUpdate", - CHANNEL_PINS_UPDATE: "channelPinsUpdate", - MESSAGE_CREATE: "message", - MESSAGE_DELETE: "messageDelete", - MESSAGE_UPDATE: "messageUpdate", - MESSAGE_BULK_DELETE: "messageDeleteBulk", - MESSAGE_REACTION_ADD: "messageReactionAdd", - MESSAGE_REACTION_REMOVE: "messageReactionRemove", - MESSAGE_REACTION_REMOVE_ALL: "messageReactionRemoveAll", - MESSAGE_REACTION_REMOVE_EMOJI: "messageReactionRemoveEmoji", - USER_UPDATE: "userUpdate", - PRESENCE_UPDATE: "presenceUpdate", - VOICE_SERVER_UPDATE: "voiceServerUpdate", - VOICE_STATE_UPDATE: "voiceStateUpdate", - VOICE_BROADCAST_SUBSCRIBE: "subscribe", - VOICE_BROADCAST_UNSUBSCRIBE: "unsubscribe", - TYPING_START: "typingStart", - TYPING_STOP: "typingStop", - WEBHOOKS_UPDATE: "webhookUpdate", - ERROR: "error", - WARN: "warn", - DEBUG: "debug", - SHARD_DISCONNECT: "shardDisconnect", - SHARD_ERROR: "shardError", - SHARD_RECONNECTING: "shardReconnecting", - SHARD_READY: "shardReady", - SHARD_RESUME: "shardResume", - INVALIDATED: "invalidated", - RAW: "raw", + RATE_LIMIT: "rateLimit", + CLIENT_READY: "ready", + GUILD_CREATE: "guildCreate", + GUILD_DELETE: "guildDelete", + GUILD_UPDATE: "guildUpdate", + GUILD_UNAVAILABLE: "guildUnavailable", + GUILD_AVAILABLE: "guildAvailable", + GUILD_MEMBER_ADD: "guildMemberAdd", + GUILD_MEMBER_REMOVE: "guildMemberRemove", + GUILD_MEMBER_UPDATE: "guildMemberUpdate", + GUILD_MEMBER_AVAILABLE: "guildMemberAvailable", + GUILD_MEMBER_SPEAKING: "guildMemberSpeaking", + GUILD_MEMBERS_CHUNK: "guildMembersChunk", + GUILD_INTEGRATIONS_UPDATE: "guildIntegrationsUpdate", + GUILD_ROLE_CREATE: "roleCreate", + GUILD_ROLE_DELETE: "roleDelete", + INVITE_CREATE: "inviteCreate", + INVITE_DELETE: "inviteDelete", + GUILD_ROLE_UPDATE: "roleUpdate", + GUILD_EMOJI_CREATE: "emojiCreate", + GUILD_EMOJI_DELETE: "emojiDelete", + GUILD_EMOJI_UPDATE: "emojiUpdate", + GUILD_BAN_ADD: "guildBanAdd", + GUILD_BAN_REMOVE: "guildBanRemove", + CHANNEL_CREATE: "channelCreate", + CHANNEL_DELETE: "channelDelete", + CHANNEL_UPDATE: "channelUpdate", + CHANNEL_PINS_UPDATE: "channelPinsUpdate", + MESSAGE_CREATE: "message", + MESSAGE_DELETE: "messageDelete", + MESSAGE_UPDATE: "messageUpdate", + MESSAGE_BULK_DELETE: "messageDeleteBulk", + MESSAGE_REACTION_ADD: "messageReactionAdd", + MESSAGE_REACTION_REMOVE: "messageReactionRemove", + MESSAGE_REACTION_REMOVE_ALL: "messageReactionRemoveAll", + MESSAGE_REACTION_REMOVE_EMOJI: "messageReactionRemoveEmoji", + USER_UPDATE: "userUpdate", + PRESENCE_UPDATE: "presenceUpdate", + VOICE_SERVER_UPDATE: "voiceServerUpdate", + VOICE_STATE_UPDATE: "voiceStateUpdate", + VOICE_BROADCAST_SUBSCRIBE: "subscribe", + VOICE_BROADCAST_UNSUBSCRIBE: "unsubscribe", + TYPING_START: "typingStart", + TYPING_STOP: "typingStop", + WEBHOOKS_UPDATE: "webhookUpdate", + ERROR: "error", + WARN: "warn", + DEBUG: "debug", + SHARD_DISCONNECT: "shardDisconnect", + SHARD_ERROR: "shardError", + SHARD_RECONNECTING: "shardReconnecting", + SHARD_READY: "shardReady", + SHARD_RESUME: "shardResume", + INVALIDATED: "invalidated", + RAW: "raw", }; export const ShardEvents = { - CLOSE: "close", - DESTROYED: "destroyed", - INVALID_SESSION: "invalidSession", - READY: "ready", - RESUMED: "resumed", - ALL_READY: "allReady", + CLOSE: "close", + DESTROYED: "destroyed", + INVALID_SESSION: "invalidSession", + READY: "ready", + RESUMED: "resumed", + ALL_READY: "allReady", }; /** @@ -188,42 +188,42 @@ * @typedef {string} WSEventType */ export const WSEvents = keyMirror([ - "READY", - "RESUMED", - "GUILD_CREATE", - "GUILD_DELETE", - "GUILD_UPDATE", - "INVITE_CREATE", - "INVITE_DELETE", - "GUILD_MEMBER_ADD", - "GUILD_MEMBER_REMOVE", - "GUILD_MEMBER_UPDATE", - "GUILD_MEMBERS_CHUNK", - "GUILD_INTEGRATIONS_UPDATE", - "GUILD_ROLE_CREATE", - "GUILD_ROLE_DELETE", - "GUILD_ROLE_UPDATE", - "GUILD_BAN_ADD", - "GUILD_BAN_REMOVE", - "GUILD_EMOJIS_UPDATE", - "CHANNEL_CREATE", - "CHANNEL_DELETE", - "CHANNEL_UPDATE", - "CHANNEL_PINS_UPDATE", - "MESSAGE_CREATE", - "MESSAGE_DELETE", - "MESSAGE_UPDATE", - "MESSAGE_DELETE_BULK", - "MESSAGE_REACTION_ADD", - "MESSAGE_REACTION_REMOVE", - "MESSAGE_REACTION_REMOVE_ALL", - "MESSAGE_REACTION_REMOVE_EMOJI", - "USER_UPDATE", - "PRESENCE_UPDATE", - "TYPING_START", - "VOICE_STATE_UPDATE", - "VOICE_SERVER_UPDATE", - "WEBHOOKS_UPDATE", + "READY", + "RESUMED", + "GUILD_CREATE", + "GUILD_DELETE", + "GUILD_UPDATE", + "INVITE_CREATE", + "INVITE_DELETE", + "GUILD_MEMBER_ADD", + "GUILD_MEMBER_REMOVE", + "GUILD_MEMBER_UPDATE", + "GUILD_MEMBERS_CHUNK", + "GUILD_INTEGRATIONS_UPDATE", + "GUILD_ROLE_CREATE", + "GUILD_ROLE_DELETE", + "GUILD_ROLE_UPDATE", + "GUILD_BAN_ADD", + "GUILD_BAN_REMOVE", + "GUILD_EMOJIS_UPDATE", + "CHANNEL_CREATE", + "CHANNEL_DELETE", + "CHANNEL_UPDATE", + "CHANNEL_PINS_UPDATE", + "MESSAGE_CREATE", + "MESSAGE_DELETE", + "MESSAGE_UPDATE", + "MESSAGE_DELETE_BULK", + "MESSAGE_REACTION_ADD", + "MESSAGE_REACTION_REMOVE", + "MESSAGE_REACTION_REMOVE_ALL", + "MESSAGE_REACTION_REMOVE_EMOJI", + "USER_UPDATE", + "PRESENCE_UPDATE", + "TYPING_START", + "VOICE_STATE_UPDATE", + "VOICE_SERVER_UPDATE", + "WEBHOOKS_UPDATE", ]); /** @@ -247,26 +247,26 @@ * @typedef {string} MessageType */ export const MessageTypes = [ - "DEFAULT", - "RECIPIENT_ADD", - "RECIPIENT_REMOVE", - "CALL", - "CHANNEL_NAME_CHANGE", - "CHANNEL_ICON_CHANGE", - "PINS_ADD", - "GUILD_MEMBER_JOIN", - "USER_PREMIUM_GUILD_SUBSCRIPTION", - "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1", - "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2", - "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3", - "CHANNEL_FOLLOW_ADD", - null, - "GUILD_DISCOVERY_DISQUALIFIED", - "GUILD_DISCOVERY_REQUALIFIED", - null, - null, - null, - "REPLY", + "DEFAULT", + "RECIPIENT_ADD", + "RECIPIENT_REMOVE", + "CALL", + "CHANNEL_NAME_CHANGE", + "CHANNEL_ICON_CHANGE", + "PINS_ADD", + "GUILD_MEMBER_JOIN", + "USER_PREMIUM_GUILD_SUBSCRIPTION", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3", + "CHANNEL_FOLLOW_ADD", + null, + "GUILD_DISCOVERY_DISQUALIFIED", + "GUILD_DISCOVERY_REQUALIFIED", + null, + null, + null, + "REPLY", ]; /** @@ -291,64 +291,64 @@ export const ActivityTypes = ["PLAYING", "STREAMING", "LISTENING", "WATCHING", "CUSTOM_STATUS", "COMPETING"]; export const ChannelTypes = { - TEXT: 0, - DM: 1, - VOICE: 2, - GROUP: 3, - CATEGORY: 4, - NEWS: 5, - STORE: 6, + TEXT: 0, + DM: 1, + VOICE: 2, + GROUP: 3, + CATEGORY: 4, + NEWS: 5, + STORE: 6, }; export const ClientApplicationAssetTypes = { - SMALL: 1, - BIG: 2, + SMALL: 1, + BIG: 2, }; export const AutomodActionTypes = { - BLOCK_MESSAGE: 1, - SEND_ALERT: 2, - TIMEOUT_MEMBER: 3, + BLOCK_MESSAGE: 1, + SEND_ALERT: 2, + TIMEOUT_MEMBER: 3, }; export const AutomodTriggerTypes = { - CUSTOM_WORDS: 1, - UNKNOWN_2: 2, - SUSPECTED_SPAM_CONTENT: 3, - COMMONLY_FLAGGED_WORDS: 4, - MENTION_SPAM: 5, + CUSTOM_WORDS: 1, + UNKNOWN_2: 2, + SUSPECTED_SPAM_CONTENT: 3, + COMMONLY_FLAGGED_WORDS: 4, + MENTION_SPAM: 5, }; export const Colors = { - DEFAULT: 0x000000, - WHITE: 0xffffff, - AQUA: 0x1abc9c, - GREEN: 0x2ecc71, - BLUE: 0x3498db, - YELLOW: 0xffff00, - PURPLE: 0x9b59b6, - LUMINOUS_VIVID_PINK: 0xe91e63, - GOLD: 0xf1c40f, - ORANGE: 0xe67e22, - RED: 0xe74c3c, - GREY: 0x95a5a6, - NAVY: 0x34495e, - DARK_AQUA: 0x11806a, - DARK_GREEN: 0x1f8b4c, - DARK_BLUE: 0x206694, - DARK_PURPLE: 0x71368a, - DARK_VIVID_PINK: 0xad1457, - DARK_GOLD: 0xc27c0e, - DARK_ORANGE: 0xa84300, - DARK_RED: 0x992d22, - DARK_GREY: 0x979c9f, - DARKER_GREY: 0x7f8c8d, - LIGHT_GREY: 0xbcc0c0, - DARK_NAVY: 0x2c3e50, - BLURPLE: 0x7289da, - GREYPLE: 0x99aab5, - DARK_BUT_NOT_BLACK: 0x2c2f33, - NOT_QUITE_BLACK: 0x23272a, + DEFAULT: 0x000000, + WHITE: 0xffffff, + AQUA: 0x1abc9c, + GREEN: 0x2ecc71, + BLUE: 0x3498db, + YELLOW: 0xffff00, + PURPLE: 0x9b59b6, + LUMINOUS_VIVID_PINK: 0xe91e63, + GOLD: 0xf1c40f, + ORANGE: 0xe67e22, + RED: 0xe74c3c, + GREY: 0x95a5a6, + NAVY: 0x34495e, + DARK_AQUA: 0x11806a, + DARK_GREEN: 0x1f8b4c, + DARK_BLUE: 0x206694, + DARK_PURPLE: 0x71368a, + DARK_VIVID_PINK: 0xad1457, + DARK_GOLD: 0xc27c0e, + DARK_ORANGE: 0xa84300, + DARK_RED: 0x992d22, + DARK_GREY: 0x979c9f, + DARKER_GREY: 0x7f8c8d, + LIGHT_GREY: 0xbcc0c0, + DARK_NAVY: 0x2c3e50, + BLURPLE: 0x7289da, + GREYPLE: 0x99aab5, + DARK_BUT_NOT_BLACK: 0x2c2f33, + NOT_QUITE_BLACK: 0x23272a, }; /** @@ -515,180 +515,180 @@ * @typedef {string} APIError */ export const DiscordApiErrors = { - //https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes - GENERAL_ERROR: new ApiError("General error (such as a malformed request body, amongst other things)", 0), - UNKNOWN_ACCOUNT: new ApiError("Unknown account", 10001), - UNKNOWN_APPLICATION: new ApiError("Unknown application", 10002), - UNKNOWN_CHANNEL: new ApiError("Unknown channel", 10003), - UNKNOWN_GUILD: new ApiError("Unknown guild", 10004), - UNKNOWN_INTEGRATION: new ApiError("Unknown integration", 10005), - UNKNOWN_INVITE: new ApiError("Unknown invite", 10006), - UNKNOWN_MEMBER: new ApiError("Unknown member", 10007), - UNKNOWN_MESSAGE: new ApiError("Unknown message", 10008), - UNKNOWN_OVERWRITE: new ApiError("Unknown permission overwrite", 10009), - UNKNOWN_PROVIDER: new ApiError("Unknown provider", 10010), - UNKNOWN_ROLE: new ApiError("Unknown role", 10011), - UNKNOWN_TOKEN: new ApiError("Unknown token", 10012), - UNKNOWN_USER: new ApiError("Unknown user", 10013), - UNKNOWN_EMOJI: new ApiError("Unknown emoji", 10014), - UNKNOWN_WEBHOOK: new ApiError("Unknown webhook", 10015, 404), - UNKNOWN_WEBHOOK_SERVICE: new ApiError("Unknown webhook service", 10016), - UNKNOWN_CONNECTION: new ApiError("Unknown connection", 10017, 400), - UNKNOWN_SESSION: new ApiError("Unknown session", 10020), - UNKNOWN_BAN: new ApiError("Unknown ban", 10026), - UNKNOWN_SKU: new ApiError("Unknown SKU", 10027), - UNKNOWN_STORE_LISTING: new ApiError("Unknown Store Listing", 10028), - UNKNOWN_ENTITLEMENT: new ApiError("Unknown entitlement", 10029), - UNKNOWN_BUILD: new ApiError("Unknown build", 10030), - UNKNOWN_LOBBY: new ApiError("Unknown lobby", 10031), - UNKNOWN_BRANCH: new ApiError("Unknown branch", 10032), - UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError("Unknown store directory layout", 10033), - UNKNOWN_REDISTRIBUTABLE: new ApiError("Unknown redistributable", 10036), - UNKNOWN_GIFT_CODE: new ApiError("Unknown gift code", 10038), - UNKNOWN_STREAM: new ApiError("Unknown stream", 10049), - UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError("Unknown premium server subscribe cooldown", 10050), - UNKNOWN_GUILD_TEMPLATE: new ApiError("Unknown guild template", 10057), - UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError("Unknown discoverable server category", 10059), - UNKNOWN_STICKER: new ApiError("Unknown sticker", 10060), - UNKNOWN_INTERACTION: new ApiError("Unknown interaction", 10062), - UNKNOWN_APPLICATION_COMMAND: new ApiError("Unknown application command", 10063), - UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError("Unknown application command permissions", 10066), - UNKNOWN_STAGE_INSTANCE: new ApiError("Unknown Stage Instance", 10067), - UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError("Unknown Guild Member Verification Form", 10068), - UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError("Unknown Guild Welcome Screen", 10069), - UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError("Unknown Guild Scheduled Event", 10070), - UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071), - BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001), - BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002), - EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError("Explicit content cannot be sent to the desired recipient(s)", 20009), - ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError("You are not authorized to perform this action on this application", 20012), - SLOWMODE_RATE_LIMIT: new ApiError("This action cannot be performed due to slowmode rate limit", 20016), - ONLY_OWNER: new ApiError("Only the owner of this account can perform this action", 20018), - ANNOUNCEMENT_RATE_LIMITS: new ApiError("This message cannot be edited due to announcement rate limits", 20022), - CHANNEL_WRITE_RATELIMIT: new ApiError("The channel you are writing has hit the write rate limit", 20028), - WORDS_NOT_ALLOWED: new ApiError("Your Stage topic, server name, server description, or channel names contain words that are not allowed", 20031), - GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError("Guild premium subscription level too low", 20035), - MAXIMUM_GUILDS: new ApiError("Maximum number of guilds reached ({})", 30001, undefined, ["100"]), - MAXIMUM_FRIENDS: new ApiError("Maximum number of friends reached ({})", 30002, undefined, ["1000"]), - MAXIMUM_PINS: new ApiError("Maximum number of pins reached for the channel ({})", 30003, undefined, ["50"]), - MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, ["10"]), - MAXIMUM_ROLES: new ApiError("Maximum number of guild roles reached ({})", 30005, undefined, ["250"]), - MAXIMUM_WEBHOOKS: new ApiError("Maximum number of webhooks reached ({})", 30007, undefined, ["10"]), - MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError("Maximum number of emojis reached", 30008), - MAXIMUM_REACTIONS: new ApiError("Maximum number of reactions reached ({})", 30010, undefined, ["20"]), - MAXIMUM_CHANNELS: new ApiError("Maximum number of guild channels reached ({})", 30013, undefined, ["500"]), - MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, ["10"]), - MAXIMUM_INVITES: new ApiError("Maximum number of invites reached ({})", 30016, undefined, ["1000"]), - MAXIMUM_ANIMATED_EMOJIS: new ApiError("Maximum number of animated emojis reached", 30018), - MAXIMUM_SERVER_MEMBERS: new ApiError("Maximum number of server members reached", 30019), - MAXIMUM_SERVER_CATEGORIES: new ApiError("Maximum number of server categories has been reached ({})", 30030, undefined, ["5"]), - GUILD_ALREADY_HAS_TEMPLATE: new ApiError("Guild already has a template", 30031), - MAXIMUM_THREAD_PARTICIPANTS: new ApiError("Max number of thread participants has been reached", 30033), - MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError("Maximum number of bans for non-guild members have been exceeded", 30035), - MAXIMUM_BANS_FETCHES: new ApiError("Maximum number of bans fetches has been reached", 30037), - MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039), - MAXIMUM_PRUNE_REQUESTS: new ApiError("Maximum number of prune requests has been reached. Try again later", 30040), - UNAUTHORIZED: new ApiError("Unauthorized. Provide a valid token and try again", 40001), - ACCOUNT_VERIFICATION_REQUIRED: new ApiError("You need to verify your account in order to perform this action", 40002), - OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError("You are opening direct messages too fast", 40003), - REQUEST_ENTITY_TOO_LARGE: new ApiError("Request entity too large. Try sending something smaller in size", 40005), - FEATURE_TEMPORARILY_DISABLED: new ApiError("This feature has been temporarily disabled server-side", 40006), - USER_BANNED: new ApiError("The user is banned from this guild", 40007), - CONNECTION_REVOKED: new ApiError("The connection has been revoked", 40012, 400), - TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError("Target user is not connected to voice", 40032), - ALREADY_CROSSPOSTED: new ApiError("This message has already been crossposted", 40033), - APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError("An application command with that name already exists", 40041), - MISSING_ACCESS: new ApiError("Missing access", 50001), - INVALID_ACCOUNT_TYPE: new ApiError("Invalid account type", 50002), - CANNOT_EXECUTE_ON_DM: new ApiError("Cannot execute action on a DM channel", 50003), - EMBED_DISABLED: new ApiError("Widget Disabled", 50004), - CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError("Cannot edit a message authored by another user", 50005), - CANNOT_SEND_EMPTY_MESSAGE: new ApiError("Cannot send an empty message", 50006), - CANNOT_MESSAGE_USER: new ApiError("Cannot send messages to this user", 50007), - CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError("Cannot send messages in a voice channel", 50008), - CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError("Channel verification level is too high for you to gain access", 50009), - OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010), - MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011), - INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012), - MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action ({})", 50013, undefined, [""]), - INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014), - NOTE_TOO_LONG: new ApiError("Note was too long", 50015), - INVALID_BULK_DELETE_QUANTITY: new ApiError("Provided too few or too many messages to delete. Must provide at least {} and fewer than {} messages to delete", 50016, undefined, [ - "2", - "100", - ]), - CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError("A message can only be pinned to the channel it was sent in", 50019), - INVALID_OR_TAKEN_INVITE_CODE: new ApiError("Invite code was either invalid or taken", 50020), - CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError("Cannot execute action on a system message", 50021), - CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError("Cannot execute action on this channel type", 50024), - INVALID_OAUTH_TOKEN: new ApiError("Invalid OAuth2 access token provided", 50025), - MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError("Missing required OAuth2 scope", 50026), - INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError("Invalid webhook token provided", 50027), - INVALID_ROLE: new ApiError("Invalid role", 50028), - INVALID_RECIPIENT: new ApiError("Invalid Recipient(s)", 50033), - BULK_DELETE_MESSAGE_TOO_OLD: new ApiError("A message provided was too old to bulk delete", 50034), - INVALID_FORM_BODY: new ApiError("Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided", 50035), - INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError("An invite was accepted to a guild the application's bot is not in", 50036), - INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041), - FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045), - INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046), - CANNOT_SELF_REDEEM_GIFT: new ApiError("Cannot self-redeem this gift", 50054), - PAYMENT_SOURCE_REQUIRED: new ApiError("Payment source required to redeem gift", 50070), - CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError("Cannot delete a channel required for Community guilds", 50074), - INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081), - CANNOT_EDIT_ARCHIVED_THREAD: new ApiError("Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread", 50083), - INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError("Invalid thread notification settings", 50084), - BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError("before value is earlier than the thread creation date", 50085), - SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError("This server is not available in your location", 50095), - SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError("This server needs monetization enabled in order to perform this action", 50097), - TWO_FACTOR_REQUIRED: new ApiError("Two factor is required for this operation", 60003), - NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError("No users with DiscordTag exist", 80004), - REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001), - RESOURCE_OVERLOADED: new ApiError("API resource is currently overloaded. Try again a little later", 130000), - STAGE_ALREADY_OPEN: new ApiError("The Stage is already open", 150006), - THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004), - THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005), - MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError("Maximum number of active threads reached", 160006), - MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError("Maximum number of active announcement threads reached", 160007), - INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError("Invalid JSON for uploaded Lottie file", 170001), - LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError("Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", 170002), - STICKER_MAXIMUM_FRAMERATE: new ApiError("Sticker maximum framerate exceeded", 170003), - STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, ["1000"]), - LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError("Lottie animation maximum dimensions exceeded", 170005), - STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError("Sticker frame rate is either too small or too large", 170006), - STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError("Sticker animation duration exceeds maximum of {} seconds", 170007, undefined, ["5"]), - AUTOMODERATOR_BLOCK: new ApiError("Message was blocked by automatic moderation", 200000), - BULK_BAN_FAILED: new ApiError("Failed to ban users", 500000), + //https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes + GENERAL_ERROR: new ApiError("General error (such as a malformed request body, amongst other things)", 0), + UNKNOWN_ACCOUNT: new ApiError("Unknown account", 10001), + UNKNOWN_APPLICATION: new ApiError("Unknown application", 10002), + UNKNOWN_CHANNEL: new ApiError("Unknown channel", 10003), + UNKNOWN_GUILD: new ApiError("Unknown guild", 10004), + UNKNOWN_INTEGRATION: new ApiError("Unknown integration", 10005), + UNKNOWN_INVITE: new ApiError("Unknown invite", 10006), + UNKNOWN_MEMBER: new ApiError("Unknown member", 10007), + UNKNOWN_MESSAGE: new ApiError("Unknown message", 10008), + UNKNOWN_OVERWRITE: new ApiError("Unknown permission overwrite", 10009), + UNKNOWN_PROVIDER: new ApiError("Unknown provider", 10010), + UNKNOWN_ROLE: new ApiError("Unknown role", 10011), + UNKNOWN_TOKEN: new ApiError("Unknown token", 10012), + UNKNOWN_USER: new ApiError("Unknown user", 10013), + UNKNOWN_EMOJI: new ApiError("Unknown emoji", 10014), + UNKNOWN_WEBHOOK: new ApiError("Unknown webhook", 10015, 404), + UNKNOWN_WEBHOOK_SERVICE: new ApiError("Unknown webhook service", 10016), + UNKNOWN_CONNECTION: new ApiError("Unknown connection", 10017, 400), + UNKNOWN_SESSION: new ApiError("Unknown session", 10020), + UNKNOWN_BAN: new ApiError("Unknown ban", 10026), + UNKNOWN_SKU: new ApiError("Unknown SKU", 10027), + UNKNOWN_STORE_LISTING: new ApiError("Unknown Store Listing", 10028), + UNKNOWN_ENTITLEMENT: new ApiError("Unknown entitlement", 10029), + UNKNOWN_BUILD: new ApiError("Unknown build", 10030), + UNKNOWN_LOBBY: new ApiError("Unknown lobby", 10031), + UNKNOWN_BRANCH: new ApiError("Unknown branch", 10032), + UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError("Unknown store directory layout", 10033), + UNKNOWN_REDISTRIBUTABLE: new ApiError("Unknown redistributable", 10036), + UNKNOWN_GIFT_CODE: new ApiError("Unknown gift code", 10038), + UNKNOWN_STREAM: new ApiError("Unknown stream", 10049), + UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError("Unknown premium server subscribe cooldown", 10050), + UNKNOWN_GUILD_TEMPLATE: new ApiError("Unknown guild template", 10057), + UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError("Unknown discoverable server category", 10059), + UNKNOWN_STICKER: new ApiError("Unknown sticker", 10060), + UNKNOWN_INTERACTION: new ApiError("Unknown interaction", 10062), + UNKNOWN_APPLICATION_COMMAND: new ApiError("Unknown application command", 10063), + UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError("Unknown application command permissions", 10066), + UNKNOWN_STAGE_INSTANCE: new ApiError("Unknown Stage Instance", 10067), + UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError("Unknown Guild Member Verification Form", 10068), + UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError("Unknown Guild Welcome Screen", 10069), + UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError("Unknown Guild Scheduled Event", 10070), + UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071), + BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001), + BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002), + EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError("Explicit content cannot be sent to the desired recipient(s)", 20009), + ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError("You are not authorized to perform this action on this application", 20012), + SLOWMODE_RATE_LIMIT: new ApiError("This action cannot be performed due to slowmode rate limit", 20016), + ONLY_OWNER: new ApiError("Only the owner of this account can perform this action", 20018), + ANNOUNCEMENT_RATE_LIMITS: new ApiError("This message cannot be edited due to announcement rate limits", 20022), + CHANNEL_WRITE_RATELIMIT: new ApiError("The channel you are writing has hit the write rate limit", 20028), + WORDS_NOT_ALLOWED: new ApiError("Your Stage topic, server name, server description, or channel names contain words that are not allowed", 20031), + GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError("Guild premium subscription level too low", 20035), + MAXIMUM_GUILDS: new ApiError("Maximum number of guilds reached ({})", 30001, undefined, ["100"]), + MAXIMUM_FRIENDS: new ApiError("Maximum number of friends reached ({})", 30002, undefined, ["1000"]), + MAXIMUM_PINS: new ApiError("Maximum number of pins reached for the channel ({})", 30003, undefined, ["50"]), + MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, ["10"]), + MAXIMUM_ROLES: new ApiError("Maximum number of guild roles reached ({})", 30005, undefined, ["250"]), + MAXIMUM_WEBHOOKS: new ApiError("Maximum number of webhooks reached ({})", 30007, undefined, ["10"]), + MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError("Maximum number of emojis reached", 30008), + MAXIMUM_REACTIONS: new ApiError("Maximum number of reactions reached ({})", 30010, undefined, ["20"]), + MAXIMUM_CHANNELS: new ApiError("Maximum number of guild channels reached ({})", 30013, undefined, ["500"]), + MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, ["10"]), + MAXIMUM_INVITES: new ApiError("Maximum number of invites reached ({})", 30016, undefined, ["1000"]), + MAXIMUM_ANIMATED_EMOJIS: new ApiError("Maximum number of animated emojis reached", 30018), + MAXIMUM_SERVER_MEMBERS: new ApiError("Maximum number of server members reached", 30019), + MAXIMUM_SERVER_CATEGORIES: new ApiError("Maximum number of server categories has been reached ({})", 30030, undefined, ["5"]), + GUILD_ALREADY_HAS_TEMPLATE: new ApiError("Guild already has a template", 30031), + MAXIMUM_THREAD_PARTICIPANTS: new ApiError("Max number of thread participants has been reached", 30033), + MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError("Maximum number of bans for non-guild members have been exceeded", 30035), + MAXIMUM_BANS_FETCHES: new ApiError("Maximum number of bans fetches has been reached", 30037), + MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039), + MAXIMUM_PRUNE_REQUESTS: new ApiError("Maximum number of prune requests has been reached. Try again later", 30040), + UNAUTHORIZED: new ApiError("Unauthorized. Provide a valid token and try again", 40001), + ACCOUNT_VERIFICATION_REQUIRED: new ApiError("You need to verify your account in order to perform this action", 40002), + OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError("You are opening direct messages too fast", 40003), + REQUEST_ENTITY_TOO_LARGE: new ApiError("Request entity too large. Try sending something smaller in size", 40005), + FEATURE_TEMPORARILY_DISABLED: new ApiError("This feature has been temporarily disabled server-side", 40006), + USER_BANNED: new ApiError("The user is banned from this guild", 40007), + CONNECTION_REVOKED: new ApiError("The connection has been revoked", 40012, 400), + TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError("Target user is not connected to voice", 40032), + ALREADY_CROSSPOSTED: new ApiError("This message has already been crossposted", 40033), + APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError("An application command with that name already exists", 40041), + MISSING_ACCESS: new ApiError("Missing access", 50001), + INVALID_ACCOUNT_TYPE: new ApiError("Invalid account type", 50002), + CANNOT_EXECUTE_ON_DM: new ApiError("Cannot execute action on a DM channel", 50003), + EMBED_DISABLED: new ApiError("Widget Disabled", 50004), + CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError("Cannot edit a message authored by another user", 50005), + CANNOT_SEND_EMPTY_MESSAGE: new ApiError("Cannot send an empty message", 50006), + CANNOT_MESSAGE_USER: new ApiError("Cannot send messages to this user", 50007), + CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError("Cannot send messages in a voice channel", 50008), + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError("Channel verification level is too high for you to gain access", 50009), + OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010), + MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011), + INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012), + MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action ({})", 50013, undefined, [""]), + INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014), + NOTE_TOO_LONG: new ApiError("Note was too long", 50015), + INVALID_BULK_DELETE_QUANTITY: new ApiError("Provided too few or too many messages to delete. Must provide at least {} and fewer than {} messages to delete", 50016, undefined, [ + "2", + "100", + ]), + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError("A message can only be pinned to the channel it was sent in", 50019), + INVALID_OR_TAKEN_INVITE_CODE: new ApiError("Invite code was either invalid or taken", 50020), + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError("Cannot execute action on a system message", 50021), + CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError("Cannot execute action on this channel type", 50024), + INVALID_OAUTH_TOKEN: new ApiError("Invalid OAuth2 access token provided", 50025), + MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError("Missing required OAuth2 scope", 50026), + INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError("Invalid webhook token provided", 50027), + INVALID_ROLE: new ApiError("Invalid role", 50028), + INVALID_RECIPIENT: new ApiError("Invalid Recipient(s)", 50033), + BULK_DELETE_MESSAGE_TOO_OLD: new ApiError("A message provided was too old to bulk delete", 50034), + INVALID_FORM_BODY: new ApiError("Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided", 50035), + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError("An invite was accepted to a guild the application's bot is not in", 50036), + INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041), + FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045), + INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046), + CANNOT_SELF_REDEEM_GIFT: new ApiError("Cannot self-redeem this gift", 50054), + PAYMENT_SOURCE_REQUIRED: new ApiError("Payment source required to redeem gift", 50070), + CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError("Cannot delete a channel required for Community guilds", 50074), + INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081), + CANNOT_EDIT_ARCHIVED_THREAD: new ApiError("Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread", 50083), + INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError("Invalid thread notification settings", 50084), + BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError("before value is earlier than the thread creation date", 50085), + SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError("This server is not available in your location", 50095), + SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError("This server needs monetization enabled in order to perform this action", 50097), + TWO_FACTOR_REQUIRED: new ApiError("Two factor is required for this operation", 60003), + NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError("No users with DiscordTag exist", 80004), + REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001), + RESOURCE_OVERLOADED: new ApiError("API resource is currently overloaded. Try again a little later", 130000), + STAGE_ALREADY_OPEN: new ApiError("The Stage is already open", 150006), + THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004), + THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005), + MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError("Maximum number of active threads reached", 160006), + MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError("Maximum number of active announcement threads reached", 160007), + INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError("Invalid JSON for uploaded Lottie file", 170001), + LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError("Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", 170002), + STICKER_MAXIMUM_FRAMERATE: new ApiError("Sticker maximum framerate exceeded", 170003), + STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, ["1000"]), + LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError("Lottie animation maximum dimensions exceeded", 170005), + STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError("Sticker frame rate is either too small or too large", 170006), + STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError("Sticker animation duration exceeds maximum of {} seconds", 170007, undefined, ["5"]), + AUTOMODERATOR_BLOCK: new ApiError("Message was blocked by automatic moderation", 200000), + BULK_BAN_FAILED: new ApiError("Failed to ban users", 500000), - //Other errors - UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404), + //Other errors + UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404), }; /** * An error encountered while performing an API request (Spacebar only). Here are the potential errors: */ export const SpacebarApiErrors = { - MANUALLY_TRIGGERED_ERROR: new ApiError("This is an artificial error", 1, 500), - PREMIUM_DISABLED_FOR_GUILD: new ApiError("This guild cannot be boosted", 25001), - NO_FURTHER_PREMIUM: new ApiError("This guild does not receive further boosts", 25002), - GUILD_PREMIUM_DISABLED_FOR_YOU: new ApiError("This guild cannot be boosted by you", 25003, 403), - CANNOT_FRIEND_SELF: new ApiError("Cannot friend oneself", 25009), - USER_SPECIFIC_INVITE_WRONG_RECIPIENT: new ApiError("This invite is not meant for you", 25010), - USER_SPECIFIC_INVITE_FAILED: new ApiError("Failed to invite user", 25011), - CANNOT_MODIFY_USER_GROUP: new ApiError("This user cannot manipulate this group", 25050, 403), - CANNOT_REMOVE_SELF_FROM_GROUP: new ApiError("This user cannot remove oneself from user group", 25051), - CANNOT_BAN_OPERATOR: new ApiError("Non-OPERATOR cannot ban OPERATOR from instance", 25052), - CANNOT_LEAVE_GUILD: new ApiError("You are not allowed to leave guilds that you joined by yourself", 25059, 403), - EDITS_DISABLED: new ApiError("You are not allowed to edit your own messages", 25060, 403), - DELETE_MESSAGE_DISABLED: new ApiError("You are not allowed to delete your own messages", 25061, 403), - FEATURE_PERMANENTLY_DISABLED: new ApiError("This feature has been disabled server-side", 45006, 501), - FEATURE_IS_IMMUTABLE: new ApiError("The feature ({}) cannot be edited.", 45007, 403), - MISSING_RIGHTS: new ApiError("You lack rights to perform that action ({})", 50013, undefined, [""]), - CANNOT_REPLACE_BY_BACKFILL: new ApiError("Cannot backfill to message ID that already exists", 55002, 409), - CANNOT_BACKFILL_TO_THE_FUTURE: new ApiError("You cannot backfill messages in the future", 55003), - CANNOT_GRANT_PERMISSIONS_EXCEEDING_RIGHTS: new ApiError("You cannot grant permissions exceeding your own rights", 50050), - ROUTES_LOOPING: new ApiError("Loops in the route definition ({})", 50060, undefined, [""]), - CANNOT_REMOVE_ROUTE: new ApiError("Cannot remove message route while it is in effect and being used", 50061), + MANUALLY_TRIGGERED_ERROR: new ApiError("This is an artificial error", 1, 500), + PREMIUM_DISABLED_FOR_GUILD: new ApiError("This guild cannot be boosted", 25001), + NO_FURTHER_PREMIUM: new ApiError("This guild does not receive further boosts", 25002), + GUILD_PREMIUM_DISABLED_FOR_YOU: new ApiError("This guild cannot be boosted by you", 25003, 403), + CANNOT_FRIEND_SELF: new ApiError("Cannot friend oneself", 25009), + USER_SPECIFIC_INVITE_WRONG_RECIPIENT: new ApiError("This invite is not meant for you", 25010), + USER_SPECIFIC_INVITE_FAILED: new ApiError("Failed to invite user", 25011), + CANNOT_MODIFY_USER_GROUP: new ApiError("This user cannot manipulate this group", 25050, 403), + CANNOT_REMOVE_SELF_FROM_GROUP: new ApiError("This user cannot remove oneself from user group", 25051), + CANNOT_BAN_OPERATOR: new ApiError("Non-OPERATOR cannot ban OPERATOR from instance", 25052), + CANNOT_LEAVE_GUILD: new ApiError("You are not allowed to leave guilds that you joined by yourself", 25059, 403), + EDITS_DISABLED: new ApiError("You are not allowed to edit your own messages", 25060, 403), + DELETE_MESSAGE_DISABLED: new ApiError("You are not allowed to delete your own messages", 25061, 403), + FEATURE_PERMANENTLY_DISABLED: new ApiError("This feature has been disabled server-side", 45006, 501), + FEATURE_IS_IMMUTABLE: new ApiError("The feature ({}) cannot be edited.", 45007, 403), + MISSING_RIGHTS: new ApiError("You lack rights to perform that action ({})", 50013, undefined, [""]), + CANNOT_REPLACE_BY_BACKFILL: new ApiError("Cannot backfill to message ID that already exists", 55002, 409), + CANNOT_BACKFILL_TO_THE_FUTURE: new ApiError("You cannot backfill messages in the future", 55003), + CANNOT_GRANT_PERMISSIONS_EXCEEDING_RIGHTS: new ApiError("You cannot grant permissions exceeding your own rights", 50050), + ROUTES_LOOPING: new ApiError("Loops in the route definition ({})", 50060, undefined, [""]), + CANNOT_REMOVE_ROUTE: new ApiError("Cannot remove message route while it is in effect and being used", 50061), }; /** @@ -719,7 +719,7 @@ export const WebhookTypes = ["Custom", "Incoming", "Channel Follower"]; function keyMirror(arr: string[]) { - const tmp = Object.create(null); - for (const value of arr) tmp[value] = value; - return tmp; + const tmp = Object.create(null); + for (const value of arr) tmp[value] = value; + return tmp; } diff --git a/src/util/util/Database.ts b/src/util/util/Database.ts index 69c83f6..35276a9 100644 --- a/src/util/util/Database.ts +++ b/src/util/util/Database.ts @@ -31,7 +31,7 @@ // For typeorm cli if (!process.env) { - config({ quiet: true }); + config({ quiet: true }); } const dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db"); @@ -40,85 +40,85 @@ const isSqlite = DatabaseType.includes("sqlite"); export const DataSourceOptions = new DataSource({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore type 'string' is not 'sqlite' | 'postgres' | etc etc - type: DatabaseType, - charset: "utf8mb4", - url: isSqlite ? undefined : dbConnectionString, - database: isSqlite ? dbConnectionString : undefined, - entities: [path.join(__dirname, "..", "entities", "*.js")], - synchronize: !!process.env.DB_SYNC, - logging: !!process.env.DB_LOGGING, - bigNumberStrings: false, - supportBigNumbers: true, - name: "default", - migrations: [path.join(__dirname, "..", "migration", DatabaseType, "*.js")], + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore type 'string' is not 'sqlite' | 'postgres' | etc etc + type: DatabaseType, + charset: "utf8mb4", + url: isSqlite ? undefined : dbConnectionString, + database: isSqlite ? dbConnectionString : undefined, + entities: [path.join(__dirname, "..", "entities", "*.js")], + synchronize: !!process.env.DB_SYNC, + logging: !!process.env.DB_LOGGING, + bigNumberStrings: false, + supportBigNumbers: true, + name: "default", + migrations: [path.join(__dirname, "..", "migration", DatabaseType, "*.js")], }); // Gets the existing database connection export function getDatabase(): DataSource | null { - // if (!dbConnection) throw new Error("Tried to get database before it was initialised"); - if (!dbConnection) return null; - return dbConnection; + // if (!dbConnection) throw new Error("Tried to get database before it was initialised"); + if (!dbConnection) return null; + return dbConnection; } // Called once on server start export async function initDatabase(): Promise { - if (dbConnection) return dbConnection; + if (dbConnection) return dbConnection; - if (isSqlite) { - console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`); - } + if (isSqlite) { + console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`); + } - if (!process.env.DB_SYNC) { - const supported = ["postgres", "sqlite"]; - if (!supported.includes(DatabaseType)) { - console.log( - "[Database]" + - red( - ` We don't have migrations for DB type '${DatabaseType}'` + - ` To ignore, set DB_SYNC=true in your env. https://docs.spacebar.chat/setup/server/configuration/env/`, - ), - ); - process.exit(); - } - } + if (!process.env.DB_SYNC) { + const supported = ["postgres", "sqlite"]; + if (!supported.includes(DatabaseType)) { + console.log( + "[Database]" + + red( + ` We don't have migrations for DB type '${DatabaseType}'` + + ` To ignore, set DB_SYNC=true in your env. https://docs.spacebar.chat/setup/server/configuration/env/`, + ), + ); + process.exit(); + } + } - console.log(`[Database] ${yellow(`Connecting to ${DatabaseType} db`)}`); + console.log(`[Database] ${yellow(`Connecting to ${DatabaseType} db`)}`); - dbConnection = await DataSourceOptions.initialize(); + dbConnection = await DataSourceOptions.initialize(); - if (DatabaseType === "sqlite") { - console.log(`[Database] ${yellow("Warning: SQLite is not supported. Forcing sync, this may lead to data loss!")}`); - await dbConnection.synchronize(); - console.log(`[Database] ${green("Connected")}`); - return dbConnection; - } + if (DatabaseType === "sqlite") { + console.log(`[Database] ${yellow("Warning: SQLite is not supported. Forcing sync, this may lead to data loss!")}`); + await dbConnection.synchronize(); + console.log(`[Database] ${green("Connected")}`); + return dbConnection; + } - // Crude way of detecting if the migrations table exists. - const dbExists = async () => { - try { - await ConfigEntity.count(); - return true; - } catch (e) { - return false; - } - }; + // Crude way of detecting if the migrations table exists. + const dbExists = async () => { + try { + await ConfigEntity.count(); + return true; + } catch (e) { + return false; + } + }; - if (!(await dbExists())) { - console.log("[Database] This appears to be a fresh database. Running initial DDL."); - const qr = dbConnection.createQueryRunner(); - if (fs.existsSync(path.join(__dirname, "..", "migration", DatabaseType, "initial0.js"))) await new (require(`../migration/${DatabaseType}-initial`).initial0)().up(qr); - } + if (!(await dbExists())) { + console.log("[Database] This appears to be a fresh database. Running initial DDL."); + const qr = dbConnection.createQueryRunner(); + if (fs.existsSync(path.join(__dirname, "..", "migration", DatabaseType, "initial0.js"))) await new (require(`../migration/${DatabaseType}-initial`).initial0)().up(qr); + } - console.log("[Database] Applying missing migrations, if any."); - await dbConnection.runMigrations(); + console.log("[Database] Applying missing migrations, if any."); + await dbConnection.runMigrations(); - console.log(`[Database] ${green("Connected")}`); + console.log(`[Database] ${green("Connected")}`); - return dbConnection; + return dbConnection; } export async function closeDatabase() { - await dbConnection?.destroy(); + await dbConnection?.destroy(); } diff --git a/src/util/util/DateBuilder.test.ts b/src/util/util/DateBuilder.test.ts index 4454f4c..2555d84 100644 --- a/src/util/util/DateBuilder.test.ts +++ b/src/util/util/DateBuilder.test.ts @@ -3,140 +3,140 @@ import { DateBuilder } from "./DateBuilder"; test("DateBuilder should be able to be initialised", () => { - const db = new DateBuilder(); - assert.equal(db instanceof DateBuilder, true); + const db = new DateBuilder(); + assert.equal(db instanceof DateBuilder, true); }); test("DateBuilder should be able to build current date", () => { - const now = new Date(); - const db = new DateBuilder(); - const built = db.build(); - assert.equal(built.getFullYear(), now.getFullYear()); - assert.equal(built.getMonth(), now.getMonth()); - assert.equal(built.getDate(), now.getDate()); - assert.equal(built.getHours(), now.getHours()); - assert.equal(built.getMinutes(), now.getMinutes()); - assert.equal(built.getSeconds(), now.getSeconds()); + const now = new Date(); + const db = new DateBuilder(); + const built = db.build(); + assert.equal(built.getFullYear(), now.getFullYear()); + assert.equal(built.getMonth(), now.getMonth()); + assert.equal(built.getDate(), now.getDate()); + assert.equal(built.getHours(), now.getHours()); + assert.equal(built.getMinutes(), now.getMinutes()); + assert.equal(built.getSeconds(), now.getSeconds()); }); test("DateBuilder should be able to build timestamp", () => { - const now = new Date(); - const db = new DateBuilder(); - const built = db.buildTimestamp(); - assert.equal(built, now.getTime()); + const now = new Date(); + const db = new DateBuilder(); + const built = db.buildTimestamp(); + assert.equal(built, now.getTime()); }); test("DateBuilder should be able to add days", () => { - const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 1, 2024 - db.addDays(30); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 0); // January - assert.equal(built.getDate(), 31); // January has 31 days + const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 1, 2024 + db.addDays(30); + const built = db.build(); + assert.equal(built.getFullYear(), 2024); + assert.equal(built.getMonth(), 0); // January + assert.equal(built.getDate(), 31); // January has 31 days }); test("DateBuilder should be able to add months", () => { - const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 31, 2024 - db.addMonths(1); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 1); // February + const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 31, 2024 + db.addMonths(1); + const built = db.build(); + assert.equal(built.getFullYear(), 2024); + assert.equal(built.getMonth(), 1); // February }); test("DateBuilder should be able to add years", () => { - const db = new DateBuilder(new Date(2020, 1, 1)); - db.addYears(1); - const built = db.build(); - assert.equal(built.getFullYear(), 2021); - assert.equal(built.getMonth(), 1); // February - assert.equal(built.getDate(), 1); + const db = new DateBuilder(new Date(2020, 1, 1)); + db.addYears(1); + const built = db.build(); + assert.equal(built.getFullYear(), 2021); + assert.equal(built.getMonth(), 1); // February + assert.equal(built.getDate(), 1); }); test("DateBuilder should be able to set date", () => { - const db = new DateBuilder(); - db.withDate(2022, 12, 25); // Dec 25, 2022 - const built = db.build(); - assert.equal(built.getFullYear(), 2022); - assert.equal(built.getMonth(), 11); // December - assert.equal(built.getDate(), 25); + const db = new DateBuilder(); + db.withDate(2022, 12, 25); // Dec 25, 2022 + const built = db.build(); + assert.equal(built.getFullYear(), 2022); + assert.equal(built.getMonth(), 11); // December + assert.equal(built.getDate(), 25); }); test("DateBuilder should be able to set time", () => { - const db = new DateBuilder(); - db.withTime(15, 30, 45, 123); // 15:30:45.123 - const built = db.build(); - assert.equal(built.getHours(), 15); - assert.equal(built.getMinutes(), 30); - assert.equal(built.getSeconds(), 45); - assert.equal(built.getMilliseconds(), 123); + const db = new DateBuilder(); + db.withTime(15, 30, 45, 123); // 15:30:45.123 + const built = db.build(); + assert.equal(built.getHours(), 15); + assert.equal(built.getMinutes(), 30); + assert.equal(built.getSeconds(), 45); + assert.equal(built.getMilliseconds(), 123); }); test("DateBuilder should be able to set start of day", () => { - const db = new DateBuilder(new Date(2024, 5, 15, 10, 20, 30, 456)); // June 15, 2024, 10:20:30.456 - db.atStartOfDay(); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 5); // June - assert.equal(built.getDate(), 15); - assert.equal(built.getHours(), 0); - assert.equal(built.getMinutes(), 0); - assert.equal(built.getSeconds(), 0); - assert.equal(built.getMilliseconds(), 0); + const db = new DateBuilder(new Date(2024, 5, 15, 10, 20, 30, 456)); // June 15, 2024, 10:20:30.456 + db.atStartOfDay(); + const built = db.build(); + assert.equal(built.getFullYear(), 2024); + assert.equal(built.getMonth(), 5); // June + assert.equal(built.getDate(), 15); + assert.equal(built.getHours(), 0); + assert.equal(built.getMinutes(), 0); + assert.equal(built.getSeconds(), 0); + assert.equal(built.getMilliseconds(), 0); }); test("DateBuilder should be able to set end of day", () => { - const db = new DateBuilder(new Date(2024, 5, 15, 10, 20, 30, 456)); // June 15, 2024, 10:20:30.456 - db.atEndOfDay(); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 5); // June - assert.equal(built.getDate(), 15); - assert.equal(built.getHours(), 23); - assert.equal(built.getMinutes(), 59); - assert.equal(built.getSeconds(), 59); - assert.equal(built.getMilliseconds(), 999); + const db = new DateBuilder(new Date(2024, 5, 15, 10, 20, 30, 456)); // June 15, 2024, 10:20:30.456 + db.atEndOfDay(); + const built = db.build(); + assert.equal(built.getFullYear(), 2024); + assert.equal(built.getMonth(), 5); // June + assert.equal(built.getDate(), 15); + assert.equal(built.getHours(), 23); + assert.equal(built.getMinutes(), 59); + assert.equal(built.getSeconds(), 59); + assert.equal(built.getMilliseconds(), 999); }); test("DateBuilder should be able to chain methods", () => { - const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 1, 2024 - db.addDays(1).addMonths(1).addYears(1).withTime(12, 0, 0).atEndOfDay(); - const built = db.build(); - assert.equal(built.getFullYear(), 2025); - assert.equal(built.getMonth(), 1); // March - assert.equal(built.getDate(), 2); - assert.equal(built.getHours(), 23); - assert.equal(built.getMinutes(), 59); - assert.equal(built.getSeconds(), 59); - assert.equal(built.getMilliseconds(), 999); + const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 1, 2024 + db.addDays(1).addMonths(1).addYears(1).withTime(12, 0, 0).atEndOfDay(); + const built = db.build(); + assert.equal(built.getFullYear(), 2025); + assert.equal(built.getMonth(), 1); // March + assert.equal(built.getDate(), 2); + assert.equal(built.getHours(), 23); + assert.equal(built.getMinutes(), 59); + assert.equal(built.getSeconds(), 59); + assert.equal(built.getMilliseconds(), 999); }); test("DateBuilder should not mutate original date", () => { - const original = new Date(2024, 0, 1); // Jan 1, 2024 - const db = new DateBuilder(original); - db.addDays(10); - const built = db.build(); - assert.equal(original.getFullYear(), 2024); - assert.equal(original.getMonth(), 0); // January - assert.equal(original.getDate(), 1); // Original date should remain unchanged - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 0); // January - assert.equal(built.getDate(), 11); // New date should be Jan 11, 2024 + const original = new Date(2024, 0, 1); // Jan 1, 2024 + const db = new DateBuilder(original); + db.addDays(10); + const built = db.build(); + assert.equal(original.getFullYear(), 2024); + assert.equal(original.getMonth(), 0); // January + assert.equal(original.getDate(), 1); // Original date should remain unchanged + assert.equal(built.getFullYear(), 2024); + assert.equal(built.getMonth(), 0); // January + assert.equal(built.getDate(), 11); // New date should be Jan 11, 2024 }); test("DateBuilder should handle leap years correctly", () => { - const db = new DateBuilder(new Date(2020, 1, 29)); // Feb 29, 2020 (leap year) - db.addYears(1); - const built = db.build(); - assert.equal(built.getFullYear(), 2021); - assert.equal(built.getMonth(), 2); // March - assert.equal(built.getDate(), 1); // March 1, 2021 (not a leap year) + const db = new DateBuilder(new Date(2020, 1, 29)); // Feb 29, 2020 (leap year) + db.addYears(1); + const built = db.build(); + assert.equal(built.getFullYear(), 2021); + assert.equal(built.getMonth(), 2); // March + assert.equal(built.getDate(), 1); // March 1, 2021 (not a leap year) }); test("DateBuilder should handle month overflow correctly", () => { - const db = new DateBuilder(new Date(2024, 0, 31)); // Jan 31, 2024 - db.addDays(1); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 1); // February - assert.equal(built.getDate(), 1); // Feb 29, 2024 (leap year) + const db = new DateBuilder(new Date(2024, 0, 31)); // Jan 31, 2024 + db.addDays(1); + const built = db.build(); + assert.equal(built.getFullYear(), 2024); + assert.equal(built.getMonth(), 1); // February + assert.equal(built.getDate(), 1); // Feb 29, 2024 (leap year) }); diff --git a/src/util/util/DateBuilder.ts b/src/util/util/DateBuilder.ts index cb71a33..981a99f 100644 --- a/src/util/util/DateBuilder.ts +++ b/src/util/util/DateBuilder.ts @@ -17,76 +17,76 @@ */ export class DateBuilder { - private date: Date; - // constructors - constructor(date = new Date()) { - if (!(date instanceof Date)) { - throw new Error("Invalid date object."); - } - this.date = new Date(date.getTime()); // Create a copy to avoid mutating the original date - } + private date: Date; + // constructors + constructor(date = new Date()) { + if (!(date instanceof Date)) { + throw new Error("Invalid date object."); + } + this.date = new Date(date.getTime()); // Create a copy to avoid mutating the original date + } - // methods - addYears(years: number) { - this.date.setFullYear(this.date.getFullYear() + years); - return this; - } + // methods + addYears(years: number) { + this.date.setFullYear(this.date.getFullYear() + years); + return this; + } - addMonths(months: number) { - this.date.setMonth(this.date.getMonth() + months); - return this; - } + addMonths(months: number) { + this.date.setMonth(this.date.getMonth() + months); + return this; + } - addDays(days: number) { - this.date.setDate(this.date.getDate() + days); - return this; - } + addDays(days: number) { + this.date.setDate(this.date.getDate() + days); + return this; + } - addHours(hours: number) { - this.date.setHours(this.date.getHours() + hours); - return this; - } + addHours(hours: number) { + this.date.setHours(this.date.getHours() + hours); + return this; + } - addMinutes(minutes: number) { - this.date.setMinutes(this.date.getMinutes() + minutes); - return this; - } + addMinutes(minutes: number) { + this.date.setMinutes(this.date.getMinutes() + minutes); + return this; + } - addSeconds(seconds: number) { - this.date.setSeconds(this.date.getSeconds() + seconds); - return this; - } + addSeconds(seconds: number) { + this.date.setSeconds(this.date.getSeconds() + seconds); + return this; + } - addMillis(millis: number) { - this.date.setTime(this.date.getTime() + millis); - return this; - } + addMillis(millis: number) { + this.date.setTime(this.date.getTime() + millis); + return this; + } - withDate(year: number, month: number, day: number | undefined) { - this.date.setFullYear(year, month - 1, day); // month is 0-based - return this; - } + withDate(year: number, month: number, day: number | undefined) { + this.date.setFullYear(year, month - 1, day); // month is 0-based + return this; + } - withTime(hour: number, minute = 0, second = 0, millisecond = 0) { - this.date.setHours(hour, minute, second, millisecond); - return this; - } + withTime(hour: number, minute = 0, second = 0, millisecond = 0) { + this.date.setHours(hour, minute, second, millisecond); + return this; + } - atStartOfDay() { - this.date.setHours(0, 0, 0, 0); - return this; - } + atStartOfDay() { + this.date.setHours(0, 0, 0, 0); + return this; + } - atEndOfDay() { - this.date.setHours(23, 59, 59, 999); - return this; - } + atEndOfDay() { + this.date.setHours(23, 59, 59, 999); + return this; + } - build() { - return new Date(this.date.getTime()); // Return a copy to avoid external mutation - } + build() { + return new Date(this.date.getTime()); // Return a copy to avoid external mutation + } - buildTimestamp() { - return this.date.getTime(); - } + buildTimestamp() { + return this.date.getTime(); + } } diff --git a/src/util/util/ElapsedTime.test.ts b/src/util/util/ElapsedTime.test.ts index dc9a82e..5a7ff5f 100644 --- a/src/util/util/ElapsedTime.test.ts +++ b/src/util/util/ElapsedTime.test.ts @@ -3,76 +3,76 @@ import { ElapsedTime } from "./ElapsedTime"; test("ElapsedTime should be able to be initialised", () => { - const db = new ElapsedTime(0n); - assert.equal(db != null, true); + const db = new ElapsedTime(0n); + assert.equal(db != null, true); }); test("ElapsedTime should return correct total nanoseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.totalNanoseconds, 1234567890n); + const db = new ElapsedTime(1234567890n); + assert.equal(db.totalNanoseconds, 1234567890n); }); test("ElapsedTime should return correct total microseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.totalMicroseconds, 1234567); + const db = new ElapsedTime(1234567890n); + assert.equal(db.totalMicroseconds, 1234567); }); test("ElapsedTime should return correct total milliseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.totalMilliseconds, 1234); + const db = new ElapsedTime(1234567890n); + assert.equal(db.totalMilliseconds, 1234); }); test("ElapsedTime should return correct total seconds", () => { - const db = new ElapsedTime(5000000000n); - assert.equal(db.totalSeconds, 5); + const db = new ElapsedTime(5000000000n); + assert.equal(db.totalSeconds, 5); }); test("ElapsedTime should return correct total minutes", () => { - const db = new ElapsedTime(300000000000n); // 5 minutes - assert.equal(db.totalMinutes, 5); + const db = new ElapsedTime(300000000000n); // 5 minutes + assert.equal(db.totalMinutes, 5); }); test("ElapsedTime should return correct total hours", () => { - const db = new ElapsedTime(7200000000000n); // 2 hours - assert.equal(db.totalHours, 2); + const db = new ElapsedTime(7200000000000n); // 2 hours + assert.equal(db.totalHours, 2); }); test("ElapsedTime should return correct total days", () => { - const db = new ElapsedTime(172800000000000n); // 2 days - assert.equal(db.totalDays, 2); + const db = new ElapsedTime(172800000000000n); // 2 days + assert.equal(db.totalDays, 2); }); test("ElapsedTime should return correct nanoseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.nanoseconds, 890); + const db = new ElapsedTime(1234567890n); + assert.equal(db.nanoseconds, 890); }); test("ElapsedTime should return correct microseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.microseconds, 567); + const db = new ElapsedTime(1234567890n); + assert.equal(db.microseconds, 567); }); test("ElapsedTime should return correct milliseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.milliseconds, 234); + const db = new ElapsedTime(1234567890n); + assert.equal(db.milliseconds, 234); }); test("ElapsedTime should return correct seconds", () => { - const db = new ElapsedTime(5000000000n); - assert.equal(db.seconds, 5); + const db = new ElapsedTime(5000000000n); + assert.equal(db.seconds, 5); }); test("ElapsedTime should return correct minutes", () => { - const db = new ElapsedTime(300000000000n); // 5 minutes - assert.equal(db.minutes, 5); + const db = new ElapsedTime(300000000000n); // 5 minutes + assert.equal(db.minutes, 5); }); test("ElapsedTime should return correct hours", () => { - const db = new ElapsedTime(7200000000000n); // 2 hours - assert.equal(db.hours, 2); + const db = new ElapsedTime(7200000000000n); // 2 hours + assert.equal(db.hours, 2); }); test("ElapsedTime should return correct days", () => { - const db = new ElapsedTime(172800000000000n); // 2 days - assert.equal(db.days, 2); + const db = new ElapsedTime(172800000000000n); // 2 days + assert.equal(db.days, 2); }); diff --git a/src/util/util/ElapsedTime.ts b/src/util/util/ElapsedTime.ts index e87b886..cce9128 100644 --- a/src/util/util/ElapsedTime.ts +++ b/src/util/util/ElapsedTime.ts @@ -20,65 +20,65 @@ // Provides a simple interface to get elapsed time in high resolution export class ElapsedTime { - private readonly timeNanos: bigint; + private readonly timeNanos: bigint; - constructor(timeNanos: bigint) { - this.timeNanos = timeNanos; - } + constructor(timeNanos: bigint) { + this.timeNanos = timeNanos; + } - get totalNanoseconds(): bigint { - return this.timeNanos; - } - get totalMicroseconds(): number { - return Number(this.timeNanos / 1_000n); - } - get totalMilliseconds(): number { - return Number(this.timeNanos / 1_000_000n); - } - get totalSeconds(): number { - return Number(this.timeNanos / 1_000_000_000n); - } - get totalMinutes(): number { - return this.totalSeconds / 60; - } - get totalHours(): number { - return this.totalMinutes / 60; - } - get totalDays(): number { - return this.totalHours / 24; - } - get nanoseconds(): number { - return Number(this.timeNanos % 1_000n); - } - get microseconds(): number { - return Number(this.timeNanos / 1_000n) % 1000; - } - get milliseconds(): number { - return Number(this.timeNanos / 1_000_000n) % 1000; - } - get seconds(): number { - return Number(this.timeNanos / 1_000_000_000n) % 60; - } - get minutes(): number { - return this.totalMinutes % 60; - } - get hours(): number { - return this.totalHours % 24; - } - get days(): number { - return this.totalDays; - } + get totalNanoseconds(): bigint { + return this.timeNanos; + } + get totalMicroseconds(): number { + return Number(this.timeNanos / 1_000n); + } + get totalMilliseconds(): number { + return Number(this.timeNanos / 1_000_000n); + } + get totalSeconds(): number { + return Number(this.timeNanos / 1_000_000_000n); + } + get totalMinutes(): number { + return this.totalSeconds / 60; + } + get totalHours(): number { + return this.totalMinutes / 60; + } + get totalDays(): number { + return this.totalHours / 24; + } + get nanoseconds(): number { + return Number(this.timeNanos % 1_000n); + } + get microseconds(): number { + return Number(this.timeNanos / 1_000n) % 1000; + } + get milliseconds(): number { + return Number(this.timeNanos / 1_000_000n) % 1000; + } + get seconds(): number { + return Number(this.timeNanos / 1_000_000_000n) % 60; + } + get minutes(): number { + return this.totalMinutes % 60; + } + get hours(): number { + return this.totalHours % 24; + } + get days(): number { + return this.totalDays; + } - toString(): string { - // Format: "DD.HH:MM:SS.mmmuuuNNN", with days being optional - const daysPart = Math.floor(this.days) > 0 ? `${Math.floor(this.days)}.` : ""; - const hoursPart = Math.floor(this.hours).toString().padStart(2, "0"); - const minutesPart = Math.floor(this.minutes).toString().padStart(2, "0"); - const secondsPart = Math.floor(this.seconds).toString().padStart(2, "0"); - const millisecondsPart = Math.floor(this.milliseconds).toString().padStart(3, "0"); - const microsecondsPart = Math.floor(this.microseconds).toString().padStart(3, "0"); - const nanosecondsPart = Math.floor(this.nanoseconds).toString().padStart(3, "0"); + toString(): string { + // Format: "DD.HH:MM:SS.mmmuuuNNN", with days being optional + const daysPart = Math.floor(this.days) > 0 ? `${Math.floor(this.days)}.` : ""; + const hoursPart = Math.floor(this.hours).toString().padStart(2, "0"); + const minutesPart = Math.floor(this.minutes).toString().padStart(2, "0"); + const secondsPart = Math.floor(this.seconds).toString().padStart(2, "0"); + const millisecondsPart = Math.floor(this.milliseconds).toString().padStart(3, "0"); + const microsecondsPart = Math.floor(this.microseconds).toString().padStart(3, "0"); + const nanosecondsPart = Math.floor(this.nanoseconds).toString().padStart(3, "0"); - return `${daysPart}${hoursPart}:${minutesPart}:${secondsPart}.${millisecondsPart}${microsecondsPart}${nanosecondsPart}`; - } + return `${daysPart}${hoursPart}:${minutesPart}:${secondsPart}.${millisecondsPart}${microsecondsPart}${nanosecondsPart}`; + } } diff --git a/src/util/util/Event.ts b/src/util/util/Event.ts index bbb0cad..9faae5f 100644 --- a/src/util/util/Event.ts +++ b/src/util/util/Event.ts @@ -31,299 +31,299 @@ let unixSocketWriter: UnixSocketWriter | null = null; export async function emitEvent(payload: Omit) { - const id = (payload.guild_id || payload.channel_id || payload.user_id) as string; - if (!id) return console.error("event doesn't contain any id", payload); + const id = (payload.guild_id || payload.channel_id || payload.user_id) as string; + if (!id) return console.error("event doesn't contain any id", payload); - if (RabbitMQ.connection) { - const data = typeof payload.data === "object" ? JSON.stringify(payload.data) : payload.data; // use rabbitmq for event transmission - const channel = await RabbitMQ.getSafeChannel(); - try { - await channel.assertExchange(id, "fanout", { - durable: false, - }); + if (RabbitMQ.connection) { + const data = typeof payload.data === "object" ? JSON.stringify(payload.data) : payload.data; // use rabbitmq for event transmission + const channel = await RabbitMQ.getSafeChannel(); + try { + await channel.assertExchange(id, "fanout", { + durable: false, + }); - // assertQueue isn't needed, because a queue will automatically created if it doesn't exist - const successful = channel.publish(id, "", Buffer.from(`${data}`), { type: payload.event }); - if (!successful) throw new Error("failed to send event"); - } catch (e) { - // todo: should we retry publishng the event? - console.log("[RabbitMQ] ", e); - } - } else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) { - if (!unixSocketWriter) { - unixSocketWriter = new UnixSocketWriter(process.env.EVENT_SOCKET_PATH); - await unixSocketWriter.init(); - } - await unixSocketWriter.emit(payload); - } else if (process.env.EVENT_TRANSMISSION === "process") { - process.send?.({ type: "event", event: payload, id } as ProcessEvent); - } else { - events.emit(id, payload); - } + // assertQueue isn't needed, because a queue will automatically created if it doesn't exist + const successful = channel.publish(id, "", Buffer.from(`${data}`), { type: payload.event }); + if (!successful) throw new Error("failed to send event"); + } catch (e) { + // todo: should we retry publishng the event? + console.log("[RabbitMQ] ", e); + } + } else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) { + if (!unixSocketWriter) { + unixSocketWriter = new UnixSocketWriter(process.env.EVENT_SOCKET_PATH); + await unixSocketWriter.init(); + } + await unixSocketWriter.emit(payload); + } else if (process.env.EVENT_TRANSMISSION === "process") { + process.send?.({ type: "event", event: payload, id } as ProcessEvent); + } else { + events.emit(id, payload); + } } export async function initEvent() { - await RabbitMQ.init(); // does nothing if rabbitmq is not setup - if (RabbitMQ.connection) { - // empty on purpose? - } else { - // use event emitter - // use process messages - } + await RabbitMQ.init(); // does nothing if rabbitmq is not setup + if (RabbitMQ.connection) { + // empty on purpose? + } else { + // use event emitter + // use process messages + } - await listenEvent("spacebar", async (event) => { - console.log("[Event] Received spacebar event:", event); - if ((event.event as string) === "SB_RELOAD_CONFIG") { - console.log("[Event] Reloading config due to RELOAD_CONFIG event"); - await Config.init(true); - } - }); + await listenEvent("spacebar", async (event) => { + console.log("[Event] Received spacebar event:", event); + if ((event.event as string) === "SB_RELOAD_CONFIG") { + console.log("[Event] Reloading config due to RELOAD_CONFIG event"); + await Config.init(true); + } + }); } export interface EventOpts extends Event { - acknowledge?: () => unknown; - channel?: Channel; - cancel: (id?: string) => unknown; + acknowledge?: () => unknown; + channel?: Channel; + cancel: (id?: string) => unknown; } export interface ListenEventOpts { - channel?: Channel; - acknowledge?: boolean; + channel?: Channel; + acknowledge?: boolean; } export interface ProcessEvent { - type: "event"; - event: Event; - id: string; + type: "event"; + event: Event; + id: string; } export async function listenEvent(event: string, callback: (event: EventOpts) => unknown, opts?: ListenEventOpts): Promise<() => Promise> { - if (RabbitMQ.connection) { - const rabbitMQChannel = await RabbitMQ.getSafeChannel(); - const channel = opts?.channel || rabbitMQChannel; - if (!channel) throw new Error("[Events] An event was sent without an associated channel"); - return await rabbitListen(channel, event, callback, { - acknowledge: opts?.acknowledge, - }); - } else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) { - if (!unixSocketListener) { - unixSocketListener = new UnixSocketListener(path.join(process.env.EVENT_SOCKET_PATH, `${process.pid}.sock`)); - await unixSocketListener.init(); - } - return await unixSocketListener.listen(event, callback); - } else if (process.env.EVENT_TRANSMISSION === "process") { - const cancel = async () => { - process.removeListener("message", listener); - process.setMaxListeners(process.getMaxListeners() - 1); - }; + if (RabbitMQ.connection) { + const rabbitMQChannel = await RabbitMQ.getSafeChannel(); + const channel = opts?.channel || rabbitMQChannel; + if (!channel) throw new Error("[Events] An event was sent without an associated channel"); + return await rabbitListen(channel, event, callback, { + acknowledge: opts?.acknowledge, + }); + } else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) { + if (!unixSocketListener) { + unixSocketListener = new UnixSocketListener(path.join(process.env.EVENT_SOCKET_PATH, `${process.pid}.sock`)); + await unixSocketListener.init(); + } + return await unixSocketListener.listen(event, callback); + } else if (process.env.EVENT_TRANSMISSION === "process") { + const cancel = async () => { + process.removeListener("message", listener); + process.setMaxListeners(process.getMaxListeners() - 1); + }; - const listener = (msg: ProcessEvent) => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - msg.type === "event" && msg.id === event && callback({ ...msg.event, cancel }); - }; + const listener = (msg: ProcessEvent) => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + msg.type === "event" && msg.id === event && callback({ ...msg.event, cancel }); + }; - // TODO: assert the type is correct? - process.addListener("message", (msg) => listener(msg as ProcessEvent)); - process.setMaxListeners(process.getMaxListeners() + 1); + // TODO: assert the type is correct? + process.addListener("message", (msg) => listener(msg as ProcessEvent)); + process.setMaxListeners(process.getMaxListeners() + 1); - return cancel; - } else { - const listener = (opts: EventOpts) => callback({ ...opts, cancel }); - const cancel = async () => { - events.removeListener(event, listener); - events.setMaxListeners(events.getMaxListeners() - 1); - }; - events.setMaxListeners(events.getMaxListeners() + 1); - events.addListener(event, listener); + return cancel; + } else { + const listener = (opts: EventOpts) => callback({ ...opts, cancel }); + const cancel = async () => { + events.removeListener(event, listener); + events.setMaxListeners(events.getMaxListeners() - 1); + }; + events.setMaxListeners(events.getMaxListeners() + 1); + events.addListener(event, listener); - return cancel; - } + return cancel; + } } async function rabbitListen(channel: Channel, id: string, callback: (event: EventOpts) => unknown, opts?: { acknowledge?: boolean }): Promise<() => Promise> { - await channel.assertExchange(id, "fanout", { durable: false }); - const q = await channel.assertQueue("", { - exclusive: true, - autoDelete: true, - }); + await channel.assertExchange(id, "fanout", { durable: false }); + const q = await channel.assertQueue("", { + exclusive: true, + autoDelete: true, + }); - const consumerTag = randomUUID(); + const consumerTag = randomUUID(); - const cancel = async () => { - await channel.cancel(consumerTag); - await channel.unbindQueue(q.queue, id, ""); - }; + const cancel = async () => { + await channel.cancel(consumerTag); + await channel.unbindQueue(q.queue, id, ""); + }; - await channel.bindQueue(q.queue, id, ""); - await channel.consume( - q.queue, - (opts) => { - if (!opts) return; + await channel.bindQueue(q.queue, id, ""); + await channel.consume( + q.queue, + (opts) => { + if (!opts) return; - const data = JSON.parse(opts.content.toString()); - const event = opts.properties.type as EVENT; + const data = JSON.parse(opts.content.toString()); + const event = opts.properties.type as EVENT; - callback({ - event, - data, - acknowledge() { - channel.ack(opts); - }, - channel, - cancel, - }); - // rabbitCh.ack(opts); - }, - { - noAck: !opts?.acknowledge, - consumerTag: consumerTag, - }, - ); + callback({ + event, + data, + acknowledge() { + channel.ack(opts); + }, + channel, + cancel, + }); + // rabbitCh.ack(opts); + }, + { + noAck: !opts?.acknowledge, + consumerTag: consumerTag, + }, + ); - return cancel; + return cancel; } class UnixSocketListener { - eventEmitter: EventEmitter; - socketPath: string; + eventEmitter: EventEmitter; + socketPath: string; - constructor(socketPath: string) { - this.eventEmitter = new EventEmitter(); - this.socketPath = socketPath; - } + constructor(socketPath: string) { + this.eventEmitter = new EventEmitter(); + this.socketPath = socketPath; + } - async init() { - const net = await import("net"); - const server = net.createServer((socket) => { - socket.on("connect", () => { - console.log("[Events] Unix socket client connected"); - }); - let buffer = Buffer.alloc(0); - socket.on("data", (data: Buffer) => { - buffer = Buffer.concat([buffer, data]); - while (buffer.length >= 4) { - const msgLen = buffer.readUInt32BE(0); - if (buffer.length < 4 + msgLen) break; - const msgBuf = buffer.slice(4, 4 + msgLen); - buffer = buffer.slice(4 + msgLen); - try { - const payload = JSON.parse(msgBuf.toString()); - this.eventEmitter.emit(payload.id, payload.event); - } catch (e) { - console.error("[Events] Failed to parse unix socket data:", e); - } - } - }); - socket.on("error", (err) => { - console.error("[Events] Unix socket error:", err); - }); - socket.on("close", () => { - console.log("[Events] Unix socket client disconnected"); - }); - }); + async init() { + const net = await import("net"); + const server = net.createServer((socket) => { + socket.on("connect", () => { + console.log("[Events] Unix socket client connected"); + }); + let buffer = Buffer.alloc(0); + socket.on("data", (data: Buffer) => { + buffer = Buffer.concat([buffer, data]); + while (buffer.length >= 4) { + const msgLen = buffer.readUInt32BE(0); + if (buffer.length < 4 + msgLen) break; + const msgBuf = buffer.slice(4, 4 + msgLen); + buffer = buffer.slice(4 + msgLen); + try { + const payload = JSON.parse(msgBuf.toString()); + this.eventEmitter.emit(payload.id, payload.event); + } catch (e) { + console.error("[Events] Failed to parse unix socket data:", e); + } + } + }); + socket.on("error", (err) => { + console.error("[Events] Unix socket error:", err); + }); + socket.on("close", () => { + console.log("[Events] Unix socket client disconnected"); + }); + }); - server.listen(this.socketPath, () => { - console.log(`Unix socket server listening on ${this.socketPath}`); - }); + server.listen(this.socketPath, () => { + console.log(`Unix socket server listening on ${this.socketPath}`); + }); - const shutdown = () => { - console.log("[Events] Closing unix socket server"); - server.close(); - process.exit(0); - }; - for (const sig of ["SIGINT", "SIGTERM", "SIGQUIT"] as const) { - process.on(sig, shutdown); - } - } + const shutdown = () => { + console.log("[Events] Closing unix socket server"); + server.close(); + process.exit(0); + }; + for (const sig of ["SIGINT", "SIGTERM", "SIGQUIT"] as const) { + process.on(sig, shutdown); + } + } - async listen(event: string, callback: (event: EventOpts) => unknown): Promise<() => Promise> { - const listener = (data: Event) => { - callback({ - ...data, - cancel, - }); - }; + async listen(event: string, callback: (event: EventOpts) => unknown): Promise<() => Promise> { + const listener = (data: Event) => { + callback({ + ...data, + cancel, + }); + }; - this.eventEmitter.addListener(event, listener); + this.eventEmitter.addListener(event, listener); - const cancel = async () => { - this.eventEmitter.removeListener(event, listener); - this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() - 1); - }; + const cancel = async () => { + this.eventEmitter.removeListener(event, listener); + this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() - 1); + }; - this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() + 1); + this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() + 1); - return cancel; - } + return cancel; + } } class UnixSocketWriter { - socketPath: string; - clients: { [key: string]: Socket } = {}; - watcher: FSWatcher; + socketPath: string; + clients: { [key: string]: Socket } = {}; + watcher: FSWatcher; - constructor(socketPath: string) { - this.socketPath = socketPath; - } + constructor(socketPath: string) { + this.socketPath = socketPath; + } - async init() { - const net = await import("net"); - const fs = await import("fs"); + async init() { + const net = await import("net"); + const fs = await import("fs"); - if (!fs.opendirSync(this.socketPath)) throw new Error("Unix socket path does not exist or is not a directory: " + this.socketPath); + if (!fs.opendirSync(this.socketPath)) throw new Error("Unix socket path does not exist or is not a directory: " + this.socketPath); - console.log("[Events] Unix socket writer initializing for", this.socketPath); + console.log("[Events] Unix socket writer initializing for", this.socketPath); - const connect = (file: string) => { - const fullPath = path.join(this.socketPath, file); - this.clients[fullPath] = net.createConnection(fullPath, () => { - console.log("[Events] Unix socket client connected to", fullPath); - }); + const connect = (file: string) => { + const fullPath = path.join(this.socketPath, file); + this.clients[fullPath] = net.createConnection(fullPath, () => { + console.log("[Events] Unix socket client connected to", fullPath); + }); - this.clients[fullPath].on("error", (err) => { - console.error("[Events] Unix socket client error on", fullPath, ":", err); - }); - }; + this.clients[fullPath].on("error", (err) => { + console.error("[Events] Unix socket client error on", fullPath, ":", err); + }); + }; - // connect to all sockets, now and in the future - this.watcher = fs.watch(this.socketPath, {}, (eventType, filename) => { - console.log("[Events] Unix socket writer received watch sig", eventType, filename); - connect(filename!); - }); + // connect to all sockets, now and in the future + this.watcher = fs.watch(this.socketPath, {}, (eventType, filename) => { + console.log("[Events] Unix socket writer received watch sig", eventType, filename); + connect(filename!); + }); - // connect to existing sockets if any - fs.readdir(this.socketPath, (err, files) => { - if (err) return console.error("[Events] Unix socket writer failed to read directory:", err); + // connect to existing sockets if any + fs.readdir(this.socketPath, (err, files) => { + if (err) return console.error("[Events] Unix socket writer failed to read directory:", err); - console.log("[Events] Unix socket writer found existing sockets:", files); - files.forEach((file) => { - connect(file); - }); - }); - } + console.log("[Events] Unix socket writer found existing sockets:", files); + files.forEach((file) => { + connect(file); + }); + }); + } - async emit(event: Event) { - if (!this.clients) throw new Error("UnixSocketWriter not initialized"); + async emit(event: Event) { + if (!this.clients) throw new Error("UnixSocketWriter not initialized"); - const tsw = Stopwatch.startNew(); - const payloadBuf = Buffer.from(JSON.stringify({ id: (event.guild_id || event.channel_id || event.user_id) as string, event })); - const lenBuf = Buffer.alloc(4); - lenBuf.writeUInt32BE(payloadBuf.length, 0); - const framed = Buffer.concat([lenBuf, payloadBuf]); - for (const socket of Object.entries(this.clients)) { - if (socket[1].destroyed) { - console.log("[Events] Unix socket writer found destroyed socket, removing:", socket[0]); - delete this.clients[socket[0]]; - continue; - } + const tsw = Stopwatch.startNew(); + const payloadBuf = Buffer.from(JSON.stringify({ id: (event.guild_id || event.channel_id || event.user_id) as string, event })); + const lenBuf = Buffer.alloc(4); + lenBuf.writeUInt32BE(payloadBuf.length, 0); + const framed = Buffer.concat([lenBuf, payloadBuf]); + for (const socket of Object.entries(this.clients)) { + if (socket[1].destroyed) { + console.log("[Events] Unix socket writer found destroyed socket, removing:", socket[0]); + delete this.clients[socket[0]]; + continue; + } - try { - socket[1].write(framed); - } catch (e) { - console.error("[Events] Unix socket writer failed to write to socket", socket[0], ":", e); - } - } + try { + socket[1].write(framed); + } catch (e) { + console.error("[Events] Unix socket writer failed to write to socket", socket[0], ":", e); + } + } - console.log(`[Events] Unix socket writer emitted to ${Object.entries(this.clients).length} sockets in ${tsw.elapsed().totalMilliseconds}ms`); - } + console.log(`[Events] Unix socket writer emitted to ${Object.entries(this.clients).length} sockets in ${tsw.elapsed().totalMilliseconds}ms`); + } } diff --git a/src/util/util/FieldError.ts b/src/util/util/FieldError.ts index ab169d0..8f93b2b 100644 --- a/src/util/util/FieldError.ts +++ b/src/util/util/FieldError.ts @@ -19,9 +19,9 @@ import { ErrorObject } from "ajv"; export interface FieldErrorResponse { - code: number; - message: string; - errors: ErrorList; + code: number; + message: string; + errors: ErrorList; } export type ErrorList = Record; @@ -29,31 +29,31 @@ export type ObjectErrorContent = { _errors: ErrorContent[] }; export function FieldErrors(fields: Record, errors?: ErrorObject[]) { - return new FieldError( - 50035, - "Invalid Form Body", - Object.values(fields).map(({ message, code }) => ({ - _errors: [ - { - message, - code: code || "BASE_TYPE_INVALID", - }, - ], - })), - errors, - ); + return new FieldError( + 50035, + "Invalid Form Body", + Object.values(fields).map(({ message, code }) => ({ + _errors: [ + { + message, + code: code || "BASE_TYPE_INVALID", + }, + ], + })), + errors, + ); } // TODO: implement Image data type: Data URI scheme that supports JPG, GIF, and PNG formats. An example Data URI format is: data:image/jpeg;base64,BASE64_ENCODED_JPEG_IMAGE_DATA // Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided. export class FieldError extends Error { - constructor( - public code: string | number, - public message: string, - public errors?: object, // TODO: I don't like this typing. - public _ajvErrors?: ErrorObject[], - ) { - super(message); - } + constructor( + public code: string | number, + public message: string, + public errors?: object, // TODO: I don't like this typing. + public _ajvErrors?: ErrorObject[], + ) { + super(message); + } } diff --git a/src/util/util/Gifs.ts b/src/util/util/Gifs.ts index 9651e23..b90f44f 100644 --- a/src/util/util/Gifs.ts +++ b/src/util/util/Gifs.ts @@ -3,22 +3,22 @@ import { TenorGif } from "@spacebar/schemas"; export function parseGifResult(result: TenorGif) { - return { - id: result.id, - title: result.title, - url: result.itemurl, - src: result.media[0].mp4.url, - gif_src: result.media[0].gif.url, - width: result.media[0].mp4.dims[0], - height: result.media[0].mp4.dims[1], - preview: result.media[0].mp4.preview, - }; + return { + id: result.id, + title: result.title, + url: result.itemurl, + src: result.media[0].mp4.url, + gif_src: result.media[0].gif.url, + width: result.media[0].mp4.dims[0], + height: result.media[0].mp4.dims[1], + preview: result.media[0].mp4.preview, + }; } export function getGifApiKey() { - const { enabled, provider, apiKey } = Config.get().gif; - if (!enabled) throw new HTTPError(`Gifs are disabled`); - if (provider !== "tenor" || !apiKey) throw new HTTPError(`${provider} gif provider not supported`); + const { enabled, provider, apiKey } = Config.get().gif; + if (!enabled) throw new HTTPError(`Gifs are disabled`); + if (provider !== "tenor" || !apiKey) throw new HTTPError(`${provider} gif provider not supported`); - return apiKey; + return apiKey; } diff --git a/src/util/util/Intents.ts b/src/util/util/Intents.ts index 0c75e7e..8d1c5a5 100644 --- a/src/util/util/Intents.ts +++ b/src/util/util/Intents.ts @@ -19,121 +19,121 @@ import { BitField } from "./BitField"; export class Intents extends BitField { - static FLAGS = { - GUILDS: BigInt(1) << BigInt(0), // guilds and guild merge-split events affecting the user - GUILD_MEMBERS: BigInt(1) << BigInt(1), // memberships - GUILD_MODERATION: BigInt(1) << BigInt(2), // bans and ban lists - GUILD_EXPRESSIONS: BigInt(1) << BigInt(3), // custom emojis - GUILD_INTEGRATIONS: BigInt(1) << BigInt(4), // applications - GUILD_WEBHOOKS: BigInt(1) << BigInt(5), // webhooks - GUILD_INVITES: BigInt(1) << BigInt(6), // mass invites (no user can receive user specific invites of another user) - GUILD_VOICE_STATES: BigInt(1) << BigInt(7), // voice updates - GUILD_PRESENCES: BigInt(1) << BigInt(8), // presence updates - GUILD_MESSAGES: BigInt(1) << BigInt(9), // guild message metadata - GUILD_MESSAGE_REACTIONS: BigInt(1) << BigInt(10), // guild message reactions - GUILD_MESSAGE_TYPING: BigInt(1) << BigInt(11), // guild channel typing notifications - DIRECT_MESSAGES: BigInt(1) << BigInt(12), // DM or orphan channels - DIRECT_MESSAGE_REACTIONS: BigInt(1) << BigInt(13), // DM or orphan channel message reactions - DIRECT_MESSAGE_TYPING: BigInt(1) << BigInt(14), // DM typing notifications - GUILD_MESSAGES_CONTENT: BigInt(1) << BigInt(15), // guild message content - AUTO_MODERATION_CONFIGURATION: BigInt(1) << BigInt(20), // guild policies - AUTO_MODERATION_EXECUTION: BigInt(1) << BigInt(21), // guild policy execution - }; + static FLAGS = { + GUILDS: BigInt(1) << BigInt(0), // guilds and guild merge-split events affecting the user + GUILD_MEMBERS: BigInt(1) << BigInt(1), // memberships + GUILD_MODERATION: BigInt(1) << BigInt(2), // bans and ban lists + GUILD_EXPRESSIONS: BigInt(1) << BigInt(3), // custom emojis + GUILD_INTEGRATIONS: BigInt(1) << BigInt(4), // applications + GUILD_WEBHOOKS: BigInt(1) << BigInt(5), // webhooks + GUILD_INVITES: BigInt(1) << BigInt(6), // mass invites (no user can receive user specific invites of another user) + GUILD_VOICE_STATES: BigInt(1) << BigInt(7), // voice updates + GUILD_PRESENCES: BigInt(1) << BigInt(8), // presence updates + GUILD_MESSAGES: BigInt(1) << BigInt(9), // guild message metadata + GUILD_MESSAGE_REACTIONS: BigInt(1) << BigInt(10), // guild message reactions + GUILD_MESSAGE_TYPING: BigInt(1) << BigInt(11), // guild channel typing notifications + DIRECT_MESSAGES: BigInt(1) << BigInt(12), // DM or orphan channels + DIRECT_MESSAGE_REACTIONS: BigInt(1) << BigInt(13), // DM or orphan channel message reactions + DIRECT_MESSAGE_TYPING: BigInt(1) << BigInt(14), // DM typing notifications + GUILD_MESSAGES_CONTENT: BigInt(1) << BigInt(15), // guild message content + AUTO_MODERATION_CONFIGURATION: BigInt(1) << BigInt(20), // guild policies + AUTO_MODERATION_EXECUTION: BigInt(1) << BigInt(21), // guild policy execution + }; - static ERKINALP_FLAGS = { - LIVE_MESSAGE_COMPOSITION: BigInt(1) << BigInt(32), // allow composing messages using the gateway - GUILD_ROUTES: BigInt(1) << BigInt(41), // message routes affecting the guild - DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42), // direct message threads - JUMBO_EVENTS: BigInt(1) << BigInt(43), // jumbo events (size limits to be defined later) - LOBBIES: BigInt(1) << BigInt(44), // lobbies - INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes - INSTANCE_GUILD_CHANGES: BigInt(1) << BigInt(61), // all guild create, guild object patch, split, merge and delete events - INSTANCE_POLICY_UPDATES: BigInt(1) << BigInt(62), // all instance policy updates - INSTANCE_USER_UPDATES: BigInt(1) << BigInt(63), // all instance user updates - }; + static ERKINALP_FLAGS = { + LIVE_MESSAGE_COMPOSITION: BigInt(1) << BigInt(32), // allow composing messages using the gateway + GUILD_ROUTES: BigInt(1) << BigInt(41), // message routes affecting the guild + DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42), // direct message threads + JUMBO_EVENTS: BigInt(1) << BigInt(43), // jumbo events (size limits to be defined later) + LOBBIES: BigInt(1) << BigInt(44), // lobbies + INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes + INSTANCE_GUILD_CHANGES: BigInt(1) << BigInt(61), // all guild create, guild object patch, split, merge and delete events + INSTANCE_POLICY_UPDATES: BigInt(1) << BigInt(62), // all instance policy updates + INSTANCE_USER_UPDATES: BigInt(1) << BigInt(63), // all instance user updates + }; - static PRIVILEGED_FLAGS: BitField = new Intents(Intents.FLAGS.GUILD_PRESENCES | Intents.FLAGS.GUILD_MEMBERS | Intents.FLAGS.GUILD_MESSAGES_CONTENT); + static PRIVILEGED_FLAGS: BitField = new Intents(Intents.FLAGS.GUILD_PRESENCES | Intents.FLAGS.GUILD_MEMBERS | Intents.FLAGS.GUILD_MESSAGES_CONTENT); - static INTENT_TO_EVENTS_MAP = { - // MESSAGE_CONTENT - 15: [], - // TODO: aren't these guild specific? - // AUTO_MODERATION_CONFIGURATION - 20: ["AUTO_MODERATION_RULE_CREATE", "AUTO_MODERATION_RULE_UPDATE", "AUTO_MODERATION_RULE_DELETE"], - // AUTO_MODERATION_EXECUTION - 21: ["AUTO_MODERATION_ACTION_EXECUTION"], - }; + static INTENT_TO_EVENTS_MAP = { + // MESSAGE_CONTENT + 15: [], + // TODO: aren't these guild specific? + // AUTO_MODERATION_CONFIGURATION + 20: ["AUTO_MODERATION_RULE_CREATE", "AUTO_MODERATION_RULE_UPDATE", "AUTO_MODERATION_RULE_DELETE"], + // AUTO_MODERATION_EXECUTION + 21: ["AUTO_MODERATION_ACTION_EXECUTION"], + }; - static GUILD_INTENT_TO_EVENTS_MAP = { - // GUILDS - 0: [ - "GUILD_CREATE", - "GUILD_UPDATE", - "GUILD_DELETE", - "GUILD_ROLE_CREATE", - "GUILD_ROLE_UPDATE", - "GUILD_ROLE_DELETE", - "CHANNEL_CREATE", - "CHANNEL_UPDATE", - "CHANNEL_DELETE", - "CHANNEL_PINS_UPDATE", - "THREAD_CREATE", - "THREAD_UPDATE", - "THREAD_DELETE", - "THREAD_LIST_SYNC", - "THREAD_MEMBER_UPDATE", - "THREAD_MEMBERS_UPDATE", // * - "STAGE_INSTANCE_CREATE", - "STAGE_INSTANCE_UPDATE", - "STAGE_INSTANCE_DELETE", - ], - // GUILD_MEMBERS - 1: [ - "GUILD_MEMBER_ADD", - "GUILD_MEMBER_UPDATE", - "GUILD_MEMBER_REMOVE", - "THREAD_MEMBERS_UPDATE ", // * - ], - // GUILD_BANS - 2: ["GUILD_AUDIT_LOG_ENTRY_CREATE", "GUILD_BAN_ADD", "GUILD_BAN_REMOVE"], - // GUILD_EXPRESSIONS - 3: [ - "GUILD_EMOJIS_UPDATE", - "GUILD_STICKERS_UPDATE", - "GUILD_SOUNDBOARD_SOUND_CREATE", - "GUILD_SOUNDBOARD_SOUND_UPDATE", - "GUILD_SOUNDBOARD_SOUND_DELETE", - "GUILD_SOUNDBOARD_SOUNDS_UPDATE", - ], - // GUILD_INTEGRATIONS - 4: ["GUILD_INTEGRATIONS_UPDATE", "INTEGRATION_CREATE", "INTEGRATION_UPDATE", "INTEGRATION_DELETE"], - // GUILD_WEBHOOKS - 5: ["WEBHOOKS_UPDATE"], - // GUILD_INVITES - 6: ["GUILD_INVITE_CREATE", "GUILD_INVITE_DELETE"], - // GUILD_VOICE_STATES - 7: ["VOICE_CHANNEL_EFFECT_SEND", "VOICE_STATE_UPDATE"], - // GUILD_PRESENCES - 8: ["PRESENCE_UPDATE"], - // GUILD_MESSAGES - 9: ["MESSAGE_CREATE", "MESSAGE_UPDATE", "MESSAGE_DELETE", "MESSAGE_DELETE_BULK"], - // GUILD_MESSAGE_REACTIONS - 10: ["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE", "MESSAGE_REACTION_REMOVE_ALL", "MESSAGE_REACTION_REMOVE_EMOJI"], - // GUILD_MESSAGE_TYPING - 11: ["TYPING_START"], - // GUILD_SCHEDULED_EVENTS - 16: ["GUILD_SCHEDULED_EVENT_CREATE", "GUILD_SCHEDULED_EVENT_UPDATE", "GUILD_SCHEDULED_EVENT_DELETE", "GUILD_SCHEDULED_EVENT_USER_ADD", "GUILD_SCHEDULED_EVENT_USER_REMOVE"], - // GUILD_MESSAGE_POLLS - 24: ["MESSAGE_POLL_VOTE_ADD", "MESSAGE_POLL_VOTE_REMOVE"], - }; - static DM_INTENT_TO_EVENTS_MAP = { - // DIRECT_MESSAGES - 12: ["MESSAGE_CREATE", "MESSAGE_UPDATE", "MESSAGE_DELETE", "CHANNEL_PINS_UPDATE"], - // DIRECT_MESSAGE_REACTIONS - 13: ["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE", "MESSAGE_REACTION_REMOVE_ALL", "MESSAGE_REACTION_REMOVE_EMOJI"], - // DIRECT_MESSAGE_TYPING - 14: ["TYPING_START"], - // DIRECT_MESSAGE_POLLS - 25: ["MESSAGE_POLL_VOTE_ADD", "MESSAGE_POLL_VOTE_REMOVE"], - }; + static GUILD_INTENT_TO_EVENTS_MAP = { + // GUILDS + 0: [ + "GUILD_CREATE", + "GUILD_UPDATE", + "GUILD_DELETE", + "GUILD_ROLE_CREATE", + "GUILD_ROLE_UPDATE", + "GUILD_ROLE_DELETE", + "CHANNEL_CREATE", + "CHANNEL_UPDATE", + "CHANNEL_DELETE", + "CHANNEL_PINS_UPDATE", + "THREAD_CREATE", + "THREAD_UPDATE", + "THREAD_DELETE", + "THREAD_LIST_SYNC", + "THREAD_MEMBER_UPDATE", + "THREAD_MEMBERS_UPDATE", // * + "STAGE_INSTANCE_CREATE", + "STAGE_INSTANCE_UPDATE", + "STAGE_INSTANCE_DELETE", + ], + // GUILD_MEMBERS + 1: [ + "GUILD_MEMBER_ADD", + "GUILD_MEMBER_UPDATE", + "GUILD_MEMBER_REMOVE", + "THREAD_MEMBERS_UPDATE ", // * + ], + // GUILD_BANS + 2: ["GUILD_AUDIT_LOG_ENTRY_CREATE", "GUILD_BAN_ADD", "GUILD_BAN_REMOVE"], + // GUILD_EXPRESSIONS + 3: [ + "GUILD_EMOJIS_UPDATE", + "GUILD_STICKERS_UPDATE", + "GUILD_SOUNDBOARD_SOUND_CREATE", + "GUILD_SOUNDBOARD_SOUND_UPDATE", + "GUILD_SOUNDBOARD_SOUND_DELETE", + "GUILD_SOUNDBOARD_SOUNDS_UPDATE", + ], + // GUILD_INTEGRATIONS + 4: ["GUILD_INTEGRATIONS_UPDATE", "INTEGRATION_CREATE", "INTEGRATION_UPDATE", "INTEGRATION_DELETE"], + // GUILD_WEBHOOKS + 5: ["WEBHOOKS_UPDATE"], + // GUILD_INVITES + 6: ["GUILD_INVITE_CREATE", "GUILD_INVITE_DELETE"], + // GUILD_VOICE_STATES + 7: ["VOICE_CHANNEL_EFFECT_SEND", "VOICE_STATE_UPDATE"], + // GUILD_PRESENCES + 8: ["PRESENCE_UPDATE"], + // GUILD_MESSAGES + 9: ["MESSAGE_CREATE", "MESSAGE_UPDATE", "MESSAGE_DELETE", "MESSAGE_DELETE_BULK"], + // GUILD_MESSAGE_REACTIONS + 10: ["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE", "MESSAGE_REACTION_REMOVE_ALL", "MESSAGE_REACTION_REMOVE_EMOJI"], + // GUILD_MESSAGE_TYPING + 11: ["TYPING_START"], + // GUILD_SCHEDULED_EVENTS + 16: ["GUILD_SCHEDULED_EVENT_CREATE", "GUILD_SCHEDULED_EVENT_UPDATE", "GUILD_SCHEDULED_EVENT_DELETE", "GUILD_SCHEDULED_EVENT_USER_ADD", "GUILD_SCHEDULED_EVENT_USER_REMOVE"], + // GUILD_MESSAGE_POLLS + 24: ["MESSAGE_POLL_VOTE_ADD", "MESSAGE_POLL_VOTE_REMOVE"], + }; + static DM_INTENT_TO_EVENTS_MAP = { + // DIRECT_MESSAGES + 12: ["MESSAGE_CREATE", "MESSAGE_UPDATE", "MESSAGE_DELETE", "CHANNEL_PINS_UPDATE"], + // DIRECT_MESSAGE_REACTIONS + 13: ["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE", "MESSAGE_REACTION_REMOVE_ALL", "MESSAGE_REACTION_REMOVE_EMOJI"], + // DIRECT_MESSAGE_TYPING + 14: ["TYPING_START"], + // DIRECT_MESSAGE_POLLS + 25: ["MESSAGE_POLL_VOTE_ADD", "MESSAGE_POLL_VOTE_REMOVE"], + }; } diff --git a/src/util/util/InvisibleCharacters.ts b/src/util/util/InvisibleCharacters.ts index 860ab74..4b4a1e4 100644 --- a/src/util/util/InvisibleCharacters.ts +++ b/src/util/util/InvisibleCharacters.ts @@ -18,58 +18,58 @@ // List from https://invisible-characters.com/ export const InvisibleCharacters = [ - "\u{9}", //Tab - "\u{c}", //Form feed - //'\u{20}', //Space //categories can have spaces in them - "\u{ad}", //Soft hyphen - //"\u{34f}", //Combining grapheme joiner - "\u{61c}", //Arabic letter mark - "\u{115f}", //Hangul choseong filler - "\u{1160}", //Hangul jungseong filler - "\u{17b4}", //Khmer vowel inherent AQ - "\u{17b5}", //Khmer vowel inherent AA - "\u{180e}", //Mongolian vowel separator - "\u{2000}", //En quad - "\u{2001}", //Em quad - "\u{2002}", //En space - "\u{2003}", //Em space - "\u{2004}", //Three-per-em space - "\u{2005}", //Four-per-em space - "\u{2006}", //Six-per-em space - "\u{2007}", //Figure space - "\u{2008}", //Punctuation space - "\u{2009}", //Thin space - "\u{200a}", //Hair space - "\u{200b}", //Zero width space - "\u{200c}", //Zero width non-joiner - //"\u{200d}", //Zero width joiner - "\u{200e}", //Left-to-right mark - "\u{200f}", //Right-to-left mark - "\u{202f}", //Narrow no-break space - "\u{205f}", //Medium mathematical space - //"\u{2060}", //Word joiner -- WJ is required in some languages that don't use spaces to split words - "\u{2061}", //Function application - "\u{2062}", //Invisible times - "\u{2063}", //Invisible separator - "\u{2064}", //Invisible plus - "\u{206a}", //Inhibit symmetric swapping - "\u{206b}", //Activate symmetric swapping - "\u{206c}", //Inhibit arabic form shaping - "\u{206d}", //Activate arabic form shaping - "\u{206e}", //National digit shapes - "\u{206f}", //Nominal digit shapes - "\u{3000}", //Ideographic space - "\u{2800}", //Braille pattern blank - "\u{3164}", //Hangul filler - "\u{feff}", //Zero width no-break space - "\u{ffa0}", //Haldwidth hangul filler - "\u{1d159}", //Musical symbol null notehead - "\u{1d173}", //Musical symbol begin beam - "\u{1d174}", //Musical symbol end beam - "\u{1d175}", //Musical symbol begin tie - "\u{1d176}", //Musical symbol end tie - "\u{1d177}", //Musical symbol begin slur - "\u{1d178}", //Musical symbol end slur - "\u{1d179}", //Musical symbol begin phrase - "\u{1d17a}", //Musical symbol end phrase + "\u{9}", //Tab + "\u{c}", //Form feed + //'\u{20}', //Space //categories can have spaces in them + "\u{ad}", //Soft hyphen + //"\u{34f}", //Combining grapheme joiner + "\u{61c}", //Arabic letter mark + "\u{115f}", //Hangul choseong filler + "\u{1160}", //Hangul jungseong filler + "\u{17b4}", //Khmer vowel inherent AQ + "\u{17b5}", //Khmer vowel inherent AA + "\u{180e}", //Mongolian vowel separator + "\u{2000}", //En quad + "\u{2001}", //Em quad + "\u{2002}", //En space + "\u{2003}", //Em space + "\u{2004}", //Three-per-em space + "\u{2005}", //Four-per-em space + "\u{2006}", //Six-per-em space + "\u{2007}", //Figure space + "\u{2008}", //Punctuation space + "\u{2009}", //Thin space + "\u{200a}", //Hair space + "\u{200b}", //Zero width space + "\u{200c}", //Zero width non-joiner + //"\u{200d}", //Zero width joiner + "\u{200e}", //Left-to-right mark + "\u{200f}", //Right-to-left mark + "\u{202f}", //Narrow no-break space + "\u{205f}", //Medium mathematical space + //"\u{2060}", //Word joiner -- WJ is required in some languages that don't use spaces to split words + "\u{2061}", //Function application + "\u{2062}", //Invisible times + "\u{2063}", //Invisible separator + "\u{2064}", //Invisible plus + "\u{206a}", //Inhibit symmetric swapping + "\u{206b}", //Activate symmetric swapping + "\u{206c}", //Inhibit arabic form shaping + "\u{206d}", //Activate arabic form shaping + "\u{206e}", //National digit shapes + "\u{206f}", //Nominal digit shapes + "\u{3000}", //Ideographic space + "\u{2800}", //Braille pattern blank + "\u{3164}", //Hangul filler + "\u{feff}", //Zero width no-break space + "\u{ffa0}", //Haldwidth hangul filler + "\u{1d159}", //Musical symbol null notehead + "\u{1d173}", //Musical symbol begin beam + "\u{1d174}", //Musical symbol end beam + "\u{1d175}", //Musical symbol begin tie + "\u{1d176}", //Musical symbol end tie + "\u{1d177}", //Musical symbol begin slur + "\u{1d178}", //Musical symbol end slur + "\u{1d179}", //Musical symbol begin phrase + "\u{1d17a}", //Musical symbol end phrase ]; diff --git a/src/util/util/JSON.ts b/src/util/util/JSON.ts index 767fc61..c8fc5fe 100644 --- a/src/util/util/JSON.ts +++ b/src/util/util/JSON.ts @@ -19,21 +19,21 @@ // Discord.com sends ISO strings with +00:00 extension, not Z // This causes issues with Python bot libs const JSONReplacer = function (this: { [key: string]: unknown }, key: string, value: unknown) { - if (this[key] instanceof Date) { - return (this[key] as Date).toISOString().replace("Z", "+00:00"); - } + if (this[key] instanceof Date) { + return (this[key] as Date).toISOString().replace("Z", "+00:00"); + } - // erlpack encoding doesn't call json.stringify, - // so our toJSON functions don't get called. - // manually call it here - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - if (this?.[key]?.toJSON) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - this[key] = this[key].toJSON(); + // erlpack encoding doesn't call json.stringify, + // so our toJSON functions don't get called. + // manually call it here + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + if (this?.[key]?.toJSON) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + this[key] = this[key].toJSON(); - return value; + return value; }; export { JSONReplacer }; diff --git a/src/util/util/KittyLogo.ts b/src/util/util/KittyLogo.ts index 5251227..56fb502 100644 --- a/src/util/util/KittyLogo.ts +++ b/src/util/util/KittyLogo.ts @@ -9,118 +9,118 @@ // }; export class KittyLogo { - static isSupported = false; - private static iconCache: string; + static isSupported = false; + private static iconCache: string; - public static async initialise() { - this.isSupported = await this.checkSupport(); - if (this.isSupported) - this.iconCache = readFileSync(__dirname + "/../../../assets/icon.png", { - encoding: "base64", - }); - } + public static async initialise() { + this.isSupported = await this.checkSupport(); + if (this.isSupported) + this.iconCache = readFileSync(__dirname + "/../../../assets/icon.png", { + encoding: "base64", + }); + } - public static printLogo(): void { - const data = readFileSync(__dirname + "/../../../assets/logo.png", { - encoding: "base64", - }); - KittyLogo.writeImage({ - base64Data: data, - width: 86, - addNewline: true, - }); - } + public static printLogo(): void { + const data = readFileSync(__dirname + "/../../../assets/logo.png", { + encoding: "base64", + }); + KittyLogo.writeImage({ + base64Data: data, + width: 86, + addNewline: true, + }); + } - public static printWithIcon(text?: string): void { - if (text) { - const lines = text.split("\n"); - for (const line of lines) { - this.writeIcon(); - process.stdout.write(" " + line + "\n"); - } - } - } + public static printWithIcon(text?: string): void { + if (text) { + const lines = text.split("\n"); + for (const line of lines) { + this.writeIcon(); + process.stdout.write(" " + line + "\n"); + } + } + } - private static writeIcon(): void { - KittyLogo.writeImage({ - base64Data: this.iconCache, - width: 2, - addNewline: false, - }); - } + private static writeIcon(): void { + KittyLogo.writeImage({ + base64Data: this.iconCache, + width: 2, + addNewline: false, + }); + } - private static checkSupport(): Promise { - if (process.env.FORCE_KITTY) return Promise.resolve(true); - // Check if we are running in a terminal - if (!process.stdin.isTTY) return Promise.resolve(false); - if (!process.stdout.isTTY) return Promise.resolve(false); + private static checkSupport(): Promise { + if (process.env.FORCE_KITTY) return Promise.resolve(true); + // Check if we are running in a terminal + if (!process.stdin.isTTY) return Promise.resolve(false); + if (!process.stdout.isTTY) return Promise.resolve(false); - // Check if we are running in a Kitty terminal - if (process.env.TERM == "xterm-kitty") return Promise.resolve(true); + // Check if we are running in a Kitty terminal + if (process.env.TERM == "xterm-kitty") return Promise.resolve(true); - // Check if we are running in a common unsupported terminal - if (process.env.TERM == "xterm") return Promise.resolve(false); - if (process.env.TERM == "xterm-256color") return Promise.resolve(false); + // Check if we are running in a common unsupported terminal + if (process.env.TERM == "xterm") return Promise.resolve(false); + if (process.env.TERM == "xterm-256color") return Promise.resolve(false); - return new Promise((resolve) => { - (async () => { - process.stdin.setEncoding("utf8"); - process.stdin.setRawMode(true); - let resp = ""; - process.stdin.once("data", function (key) { - process.stdin.setRawMode(false); - process.stdin.pause(); - resp = key.toString(); - if (resp.startsWith("\x1B_Gi=31;OK")) resolve(true); - else resolve(false); - }); - process.stdout.write("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c"); + return new Promise((resolve) => { + (async () => { + process.stdin.setEncoding("utf8"); + process.stdin.setRawMode(true); + let resp = ""; + process.stdin.once("data", function (key) { + process.stdin.setRawMode(false); + process.stdin.pause(); + resp = key.toString(); + if (resp.startsWith("\x1B_Gi=31;OK")) resolve(true); + else resolve(false); + }); + process.stdout.write("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c"); - await new Promise((res) => setTimeout(res, 5000)); - resolve(false); - })(); - }); - } + await new Promise((res) => setTimeout(res, 5000)); + resolve(false); + })(); + }); + } - private static writeImage(request: KittyImageMetadata) { - if (!this.isSupported) return; - let pngData = request.base64Data; + private static writeImage(request: KittyImageMetadata) { + if (!this.isSupported) return; + let pngData = request.base64Data; - // Ga=T,q=2,o=z,s=1022,v=181,X=5; - const chunkSize = 4096; + // Ga=T,q=2,o=z,s=1022,v=181,X=5; + const chunkSize = 4096; - //#region Header - let header = `\x1b_G`; // enable graphics - header += "a=T"; // action = transmit & display - header += ",q=2"; // suppress response - header += ",f=100"; // image format = png - header += ",t=d"; // transmission = direct - header += ",x=0"; // current x position - header += ",y=0"; // current y position - if (request.width) header += `,c=${request.width}`; // width (columns) - if (request.height) header += `,r=${request.height}`; // height (rows) - if (request.widthPixels) header += `,w=${request.widthPixels}`; // width (pixels) - if (request.heightPixels) header += `,h=${request.heightPixels}`; // height (pixels) - //#endregion + //#region Header + let header = `\x1b_G`; // enable graphics + header += "a=T"; // action = transmit & display + header += ",q=2"; // suppress response + header += ",f=100"; // image format = png + header += ",t=d"; // transmission = direct + header += ",x=0"; // current x position + header += ",y=0"; // current y position + if (request.width) header += `,c=${request.width}`; // width (columns) + if (request.height) header += `,r=${request.height}`; // height (rows) + if (request.widthPixels) header += `,w=${request.widthPixels}`; // width (pixels) + if (request.heightPixels) header += `,h=${request.heightPixels}`; // height (pixels) + //#endregion - while (pngData.length > 0) { - const dataSize = Math.min(pngData.length, chunkSize); + while (pngData.length > 0) { + const dataSize = Math.min(pngData.length, chunkSize); - process.stdout.write(header + `,m=${dataSize == chunkSize ? 1 : 0};`); - process.stdout.write(pngData.slice(0, chunkSize)); - pngData = pngData.slice(chunkSize); - process.stdout.write("\x1b\\"); - } + process.stdout.write(header + `,m=${dataSize == chunkSize ? 1 : 0};`); + process.stdout.write(pngData.slice(0, chunkSize)); + pngData = pngData.slice(chunkSize); + process.stdout.write("\x1b\\"); + } - if (request.addNewline) process.stdout.write("\n"); - } + if (request.addNewline) process.stdout.write("\n"); + } } export interface KittyImageMetadata { - base64Data: string; - width?: number; - height?: number; - widthPixels?: number; - heightPixels?: number; - addNewline?: boolean; + base64Data: string; + width?: number; + height?: number; + widthPixels?: number; + heightPixels?: number; + addNewline?: boolean; } diff --git a/src/util/util/Logo.ts b/src/util/util/Logo.ts index c38ba4b..a1daebb 100644 --- a/src/util/util/Logo.ts +++ b/src/util/util/Logo.ts @@ -2,19 +2,19 @@ import { blueBright } from "picocolors"; export class Logo { - public static async printLogo() { - await KittyLogo.initialise(); - if (KittyLogo.isSupported) KittyLogo.printLogo(); - else console.log(this.AsciiLogo); - } + public static async printLogo() { + await KittyLogo.initialise(); + if (KittyLogo.isSupported) KittyLogo.printLogo(); + else console.log(this.AsciiLogo); + } - private static AsciiLogo = blueBright( - ` + private static AsciiLogo = blueBright( + ` ████ ████ ███████╗██████╗ █████╗ ██████╗███████╗██████╗ █████╗ ██████╗ ███████ ███████ ██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗██╔══██╗ ████████████████ ███████╗██████╔╝███████║██║ █████╗ ██████╔╝███████║██████╔╝ █████ ██ █████ ╚════██║██╔═══╝ ██╔══██║██║ ██╔══╝ ██╔══██╗██╔══██║██╔══██╗ ██████████████████ ███████║██║ ██║ ██║╚██████╗███████╗██████╔╝██║ ██║██║ ██║ ████████████████ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝`.substring(1), - ); + ); } diff --git a/src/util/util/MessageFlags.ts b/src/util/util/MessageFlags.ts index f258801..84eca46 100644 --- a/src/util/util/MessageFlags.ts +++ b/src/util/util/MessageFlags.ts @@ -5,14 +5,14 @@ import { BitField } from "./BitField"; export class MessageFlags extends BitField { - static FLAGS = { - CROSSPOSTED: BigInt(1) << BigInt(0), - IS_CROSSPOST: BigInt(1) << BigInt(1), - SUPPRESS_EMBEDS: BigInt(1) << BigInt(2), - // SOURCE_MESSAGE_DELETED: BigInt(1) << BigInt(3), // spacebar will delete them from destination too, making this redundant - URGENT: BigInt(1) << BigInt(4), - // HAS_THREAD: BigInt(1) << BigInt(5) // does not apply to spacebar due to infrastructural differences - EPHEMERAL: BigInt(1) << BigInt(6), // it that has been routed to only some of the users that can see the channel - INTERACTION_WAIT: BigInt(1) << BigInt(7), // discord.com calls this LOADING - }; + static FLAGS = { + CROSSPOSTED: BigInt(1) << BigInt(0), + IS_CROSSPOST: BigInt(1) << BigInt(1), + SUPPRESS_EMBEDS: BigInt(1) << BigInt(2), + // SOURCE_MESSAGE_DELETED: BigInt(1) << BigInt(3), // spacebar will delete them from destination too, making this redundant + URGENT: BigInt(1) << BigInt(4), + // HAS_THREAD: BigInt(1) << BigInt(5) // does not apply to spacebar due to infrastructural differences + EPHEMERAL: BigInt(1) << BigInt(6), // it that has been routed to only some of the users that can see the channel + INTERACTION_WAIT: BigInt(1) << BigInt(7), // discord.com calls this LOADING + }; } diff --git a/src/util/util/NameValidation.ts b/src/util/util/NameValidation.ts index a630453..5536670 100755 --- a/src/util/util/NameValidation.ts +++ b/src/util/util/NameValidation.ts @@ -21,37 +21,37 @@ import { HTTPError } from "lambert-server"; export function ValidateName(name: string) { - const check_username = name.replace(/\s/g, ""); - if (!check_username) { - throw FieldErrors({ - username: { - code: "BASE_TYPE_REQUIRED", - message: "common:field.BASE_TYPE_REQUIRED", - }, - }); - } - const general = Config.get(); - const { maxUsername } = general.limits.user; - if (check_username.length > maxUsername || check_username.length < 2) { - throw FieldErrors({ - username: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 2 and ${maxUsername} in length.`, - }, - }); - } + const check_username = name.replace(/\s/g, ""); + if (!check_username) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_REQUIRED", + message: "common:field.BASE_TYPE_REQUIRED", + }, + }); + } + const general = Config.get(); + const { maxUsername } = general.limits.user; + if (check_username.length > maxUsername || check_username.length < 2) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 2 and ${maxUsername} in length.`, + }, + }); + } - const { blockedContains, blockedEquals } = general.user; - for (const word of blockedContains) { - if (name.toLowerCase().includes(word)) { - throw new HTTPError(`Username cannot contain "${word}"`, 400); - } - } + const { blockedContains, blockedEquals } = general.user; + for (const word of blockedContains) { + if (name.toLowerCase().includes(word)) { + throw new HTTPError(`Username cannot contain "${word}"`, 400); + } + } - for (const word of blockedEquals) { - if (name.toLowerCase() === word) { - throw new HTTPError(`Username cannot be "${word}"`, 400); - } - } - return name; + for (const word of blockedEquals) { + if (name.toLowerCase() === word) { + throw new HTTPError(`Username cannot be "${word}"`, 400); + } + } + return name; } diff --git a/src/util/util/Permissions.ts b/src/util/util/Permissions.ts index a50c23b..49bfb5f 100644 --- a/src/util/util/Permissions.ts +++ b/src/util/util/Permissions.ts @@ -15,312 +15,312 @@ // const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(64); // 27 permission bits left for discord to add new ones export class Permissions extends BitField { - cache: PermissionCache = {}; + cache: PermissionCache = {}; - constructor(bits: BitFieldResolvable = 0) { - super(bits); - if (this.bitfield & Permissions.FLAGS.ADMINISTRATOR) { - this.bitfield = Permissions.ALL_PERMISSIONS; - } - } + constructor(bits: BitFieldResolvable = 0) { + super(bits); + if (this.bitfield & Permissions.FLAGS.ADMINISTRATOR) { + this.bitfield = Permissions.ALL_PERMISSIONS; + } + } - static FLAGS = { - CREATE_INSTANT_INVITE: BitFlag(0), - KICK_MEMBERS: BitFlag(1), - BAN_MEMBERS: BitFlag(2), - ADMINISTRATOR: BitFlag(3), - MANAGE_CHANNELS: BitFlag(4), - MANAGE_GUILD: BitFlag(5), - ADD_REACTIONS: BitFlag(6), - VIEW_AUDIT_LOG: BitFlag(7), - PRIORITY_SPEAKER: BitFlag(8), - STREAM: BitFlag(9), - VIEW_CHANNEL: BitFlag(10), - SEND_MESSAGES: BitFlag(11), - SEND_TTS_MESSAGES: BitFlag(12), - MANAGE_MESSAGES: BitFlag(13), - EMBED_LINKS: BitFlag(14), - ATTACH_FILES: BitFlag(15), - READ_MESSAGE_HISTORY: BitFlag(16), - MENTION_EVERYONE: BitFlag(17), - USE_EXTERNAL_EMOJIS: BitFlag(18), - VIEW_GUILD_INSIGHTS: BitFlag(19), - CONNECT: BitFlag(20), - SPEAK: BitFlag(21), - MUTE_MEMBERS: BitFlag(22), - DEAFEN_MEMBERS: BitFlag(23), - MOVE_MEMBERS: BitFlag(24), - USE_VAD: BitFlag(25), - CHANGE_NICKNAME: BitFlag(26), - MANAGE_NICKNAMES: BitFlag(27), - MANAGE_ROLES: BitFlag(28), - MANAGE_WEBHOOKS: BitFlag(29), - MANAGE_EMOJIS_AND_STICKERS: BitFlag(30), - USE_APPLICATION_COMMANDS: BitFlag(31), - REQUEST_TO_SPEAK: BitFlag(32), - MANAGE_EVENTS: BitFlag(33), - MANAGE_THREADS: BitFlag(34), - USE_PUBLIC_THREADS: BitFlag(35), - USE_PRIVATE_THREADS: BitFlag(36), - USE_EXTERNAL_STICKERS: BitFlag(37), - SEND_MESSAGES_IN_THREADS: BitFlag(38), - USE_EMBEDDED_ACTIVITIES: BitFlag(39), - MODERATE_MEMBERS: BitFlag(40), - VIEW_CREATOR_MONETIZATION_ANALYTICS: BitFlag(41), - USE_SOUNDBOARD: BitFlag(42), - CREATE_GUILD_EXPRESSIONS: BitFlag(43), - CREATE_EVENTS: BitFlag(44), - USE_EXTERNAL_SOUNDS: BitFlag(45), - SEND_VOICE_MESSAGES: BitFlag(46), - DEPRECATED_USE_CLYDE_AI: BitFlag(47), // DEPRECATED - SET_VOICE_CHANNEL_STATUS: BitFlag(48), - SEND_POLLS: BitFlag(49), - USE_EXTERNAL_APPS: BitFlag(50), - PIN_MESSAGES: BitFlag(51), - BYPASS_SLOWMODE: BitFlag(52), + static FLAGS = { + CREATE_INSTANT_INVITE: BitFlag(0), + KICK_MEMBERS: BitFlag(1), + BAN_MEMBERS: BitFlag(2), + ADMINISTRATOR: BitFlag(3), + MANAGE_CHANNELS: BitFlag(4), + MANAGE_GUILD: BitFlag(5), + ADD_REACTIONS: BitFlag(6), + VIEW_AUDIT_LOG: BitFlag(7), + PRIORITY_SPEAKER: BitFlag(8), + STREAM: BitFlag(9), + VIEW_CHANNEL: BitFlag(10), + SEND_MESSAGES: BitFlag(11), + SEND_TTS_MESSAGES: BitFlag(12), + MANAGE_MESSAGES: BitFlag(13), + EMBED_LINKS: BitFlag(14), + ATTACH_FILES: BitFlag(15), + READ_MESSAGE_HISTORY: BitFlag(16), + MENTION_EVERYONE: BitFlag(17), + USE_EXTERNAL_EMOJIS: BitFlag(18), + VIEW_GUILD_INSIGHTS: BitFlag(19), + CONNECT: BitFlag(20), + SPEAK: BitFlag(21), + MUTE_MEMBERS: BitFlag(22), + DEAFEN_MEMBERS: BitFlag(23), + MOVE_MEMBERS: BitFlag(24), + USE_VAD: BitFlag(25), + CHANGE_NICKNAME: BitFlag(26), + MANAGE_NICKNAMES: BitFlag(27), + MANAGE_ROLES: BitFlag(28), + MANAGE_WEBHOOKS: BitFlag(29), + MANAGE_EMOJIS_AND_STICKERS: BitFlag(30), + USE_APPLICATION_COMMANDS: BitFlag(31), + REQUEST_TO_SPEAK: BitFlag(32), + MANAGE_EVENTS: BitFlag(33), + MANAGE_THREADS: BitFlag(34), + USE_PUBLIC_THREADS: BitFlag(35), + USE_PRIVATE_THREADS: BitFlag(36), + USE_EXTERNAL_STICKERS: BitFlag(37), + SEND_MESSAGES_IN_THREADS: BitFlag(38), + USE_EMBEDDED_ACTIVITIES: BitFlag(39), + MODERATE_MEMBERS: BitFlag(40), + VIEW_CREATOR_MONETIZATION_ANALYTICS: BitFlag(41), + USE_SOUNDBOARD: BitFlag(42), + CREATE_GUILD_EXPRESSIONS: BitFlag(43), + CREATE_EVENTS: BitFlag(44), + USE_EXTERNAL_SOUNDS: BitFlag(45), + SEND_VOICE_MESSAGES: BitFlag(46), + DEPRECATED_USE_CLYDE_AI: BitFlag(47), // DEPRECATED + SET_VOICE_CHANNEL_STATUS: BitFlag(48), + SEND_POLLS: BitFlag(49), + USE_EXTERNAL_APPS: BitFlag(50), + PIN_MESSAGES: BitFlag(51), + BYPASS_SLOWMODE: BitFlag(52), - /** - * CUSTOM PERMISSIONS ideas: - * - allow user to dm members - * - allow user to pin messages (without MANAGE_MESSAGES) - * - allow user to publish messages (without MANAGE_MESSAGES) - */ - // CUSTOM_PERMISSION: BigInt(1) << BigInt(0) + CUSTOM_PERMISSION_OFFSET - }; + /** + * CUSTOM PERMISSIONS ideas: + * - allow user to dm members + * - allow user to pin messages (without MANAGE_MESSAGES) + * - allow user to publish messages (without MANAGE_MESSAGES) + */ + // CUSTOM_PERMISSION: BigInt(1) << BigInt(0) + CUSTOM_PERMISSION_OFFSET + }; - static ALL_PERMISSIONS = Object.values(Permissions.FLAGS).reduce((total, val) => total | val, BigInt(0)); + static ALL_PERMISSIONS = Object.values(Permissions.FLAGS).reduce((total, val) => total | val, BigInt(0)); - any(permission: PermissionResolvable, checkAdmin = true) { - return (checkAdmin && super.any(Permissions.FLAGS.ADMINISTRATOR)) || super.any(permission); - } + any(permission: PermissionResolvable, checkAdmin = true) { + return (checkAdmin && super.any(Permissions.FLAGS.ADMINISTRATOR)) || super.any(permission); + } - /** - * Checks whether the bitfield has a permission, or multiple permissions. - */ - has(permission: PermissionResolvable, checkAdmin = true) { - return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.has(permission); - } + /** + * Checks whether the bitfield has a permission, or multiple permissions. + */ + has(permission: PermissionResolvable, checkAdmin = true) { + return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.has(permission); + } - /** - * Checks whether the bitfield has a permission, or multiple permissions, but throws an Error if user fails to match auth criteria. - */ - hasThrow(permission: PermissionResolvable) { - if (this.has(permission) && this.has("VIEW_CHANNEL")) return true; - throw new HTTPError(`You are missing the following permissions ${permission}`, 403); - } + /** + * Checks whether the bitfield has a permission, or multiple permissions, but throws an Error if user fails to match auth criteria. + */ + hasThrow(permission: PermissionResolvable) { + if (this.has(permission) && this.has("VIEW_CHANNEL")) return true; + throw new HTTPError(`You are missing the following permissions ${permission}`, 403); + } - overwriteChannel(overwrites: ChannelPermissionOverwrite[]) { - if (!overwrites) return this; - if (!this.cache) throw new Error("permission cache not available"); - overwrites = overwrites.filter((x) => { - if (x.type === ChannelPermissionOverwriteType.role && this.cache.roles?.some((r) => r.id === x.id)) return true; - if (x.type === ChannelPermissionOverwriteType.member && x.id == this.cache.user_id) return true; - return false; - }); - return new Permissions(Permissions.channelPermission(overwrites, this.bitfield)); - } + overwriteChannel(overwrites: ChannelPermissionOverwrite[]) { + if (!overwrites) return this; + if (!this.cache) throw new Error("permission cache not available"); + overwrites = overwrites.filter((x) => { + if (x.type === ChannelPermissionOverwriteType.role && this.cache.roles?.some((r) => r.id === x.id)) return true; + if (x.type === ChannelPermissionOverwriteType.member && x.id == this.cache.user_id) return true; + return false; + }); + return new Permissions(Permissions.channelPermission(overwrites, this.bitfield)); + } - static channelPermission(overwrites: ChannelPermissionOverwrite[], init?: bigint) { - // TODO: do not deny any permissions if admin - return overwrites.reduce( - (permission, overwrite) => { - // apply disallowed permission - // * permission: current calculated permission (e.g. 010) - // * deny contains all denied permissions (e.g. 011) - // * allow contains all explicitly allowed permisions (e.g. 100) - return (permission & ~BigInt(overwrite.deny)) | BigInt(overwrite.allow); - // ~ operator inverts deny (e.g. 011 -> 100) - // & operator only allows 1 for both ~deny and permission (e.g. 010 & 100 -> 000) - // | operators adds both together (e.g. 000 + 100 -> 100) - }, - init || BigInt(0), - ); - } + static channelPermission(overwrites: ChannelPermissionOverwrite[], init?: bigint) { + // TODO: do not deny any permissions if admin + return overwrites.reduce( + (permission, overwrite) => { + // apply disallowed permission + // * permission: current calculated permission (e.g. 010) + // * deny contains all denied permissions (e.g. 011) + // * allow contains all explicitly allowed permisions (e.g. 100) + return (permission & ~BigInt(overwrite.deny)) | BigInt(overwrite.allow); + // ~ operator inverts deny (e.g. 011 -> 100) + // & operator only allows 1 for both ~deny and permission (e.g. 010 & 100 -> 000) + // | operators adds both together (e.g. 000 + 100 -> 100) + }, + init || BigInt(0), + ); + } - static rolePermission(roles: Role[]) { - // adds all permissions of all roles together (Bit OR) - return roles.reduce((permission, role) => permission | BigInt(role.permissions), BigInt(0)); - } + static rolePermission(roles: Role[]) { + // adds all permissions of all roles together (Bit OR) + return roles.reduce((permission, role) => permission | BigInt(role.permissions), BigInt(0)); + } - static finalPermission({ - user, - guild, - channel, - }: { - user: { id: string; roles: string[]; communication_disabled_until: Date | null; flags: number }; - guild: { id: string; owner_id: string; roles: Role[] }; - channel?: { - overwrites?: ChannelPermissionOverwrite[]; - recipient_ids?: string[] | null; - owner_id?: string; - }; - }) { - if (user.id === "0") return new Permissions("ADMINISTRATOR"); // system user id - if (guild?.owner_id === user.id) return new Permissions(Permissions.ALL); + static finalPermission({ + user, + guild, + channel, + }: { + user: { id: string; roles: string[]; communication_disabled_until: Date | null; flags: number }; + guild: { id: string; owner_id: string; roles: Role[] }; + channel?: { + overwrites?: ChannelPermissionOverwrite[]; + recipient_ids?: string[] | null; + owner_id?: string; + }; + }) { + if (user.id === "0") return new Permissions("ADMINISTRATOR"); // system user id + if (guild?.owner_id === user.id) return new Permissions(Permissions.ALL); - const roles = guild.roles.filter((x) => user.roles.includes(x.id)); - let permission = Permissions.rolePermission(roles); + const roles = guild.roles.filter((x) => user.roles.includes(x.id)); + let permission = Permissions.rolePermission(roles); - if (channel?.overwrites) { - const overwrites = channel.overwrites.filter((x) => { - if (x.type === ChannelPermissionOverwriteType.role && user.roles.includes(x.id)) return true; - if (x.type === ChannelPermissionOverwriteType.member && x.id == user.id) return true; - return false; - }); - permission = Permissions.channelPermission(overwrites, permission); - } + if (channel?.overwrites) { + const overwrites = channel.overwrites.filter((x) => { + if (x.type === ChannelPermissionOverwriteType.role && user.roles.includes(x.id)) return true; + if (x.type === ChannelPermissionOverwriteType.member && x.id == user.id) return true; + return false; + }); + permission = Permissions.channelPermission(overwrites, permission); + } - if (channel?.recipient_ids) { - if (channel?.owner_id === user.id) return new Permissions("ADMINISTRATOR"); - if (channel.recipient_ids.includes(user.id)) { - // Default dm permissions - return new Permissions([ - "VIEW_CHANNEL", - "SEND_MESSAGES", - "STREAM", - "ADD_REACTIONS", - "EMBED_LINKS", - "ATTACH_FILES", - "READ_MESSAGE_HISTORY", - "MENTION_EVERYONE", - "USE_EXTERNAL_EMOJIS", - "CONNECT", - "SPEAK", - "MANAGE_CHANNELS", - ]); - } + if (channel?.recipient_ids) { + if (channel?.owner_id === user.id) return new Permissions("ADMINISTRATOR"); + if (channel.recipient_ids.includes(user.id)) { + // Default dm permissions + return new Permissions([ + "VIEW_CHANNEL", + "SEND_MESSAGES", + "STREAM", + "ADD_REACTIONS", + "EMBED_LINKS", + "ATTACH_FILES", + "READ_MESSAGE_HISTORY", + "MENTION_EVERYONE", + "USE_EXTERNAL_EMOJIS", + "CONNECT", + "SPEAK", + "MANAGE_CHANNELS", + ]); + } - return new Permissions(); - } + return new Permissions(); + } - if (user.communication_disabled_until) { - if (user.communication_disabled_until > new Date()) return new Permissions(permission & Permissions.TIMED_OUT_MASK.bitfield); - else { - user.communication_disabled_until = null; - Member.update({ id: user.id, guild_id: guild.id }, { communication_disabled_until: null }).catch((_) => { - // ignored - }); - } - } - if ((BigInt(user.flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) { - permission = permission & Permissions.QUARANTINED_MASK.bitfield; - } + if (user.communication_disabled_until) { + if (user.communication_disabled_until > new Date()) return new Permissions(permission & Permissions.TIMED_OUT_MASK.bitfield); + else { + user.communication_disabled_until = null; + Member.update({ id: user.id, guild_id: guild.id }, { communication_disabled_until: null }).catch((_) => { + // ignored + }); + } + } + if ((BigInt(user.flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) { + permission = permission & Permissions.QUARANTINED_MASK.bitfield; + } - return new Permissions(permission); - } + return new Permissions(permission); + } - static NONE: Permissions = new Permissions(0); - static TIMED_OUT_MASK: Permissions = new Permissions(Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.READ_MESSAGE_HISTORY); - static QUARANTINED_MASK: Permissions = new Permissions(Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.READ_MESSAGE_HISTORY | Permissions.FLAGS.CHANGE_NICKNAME); - static DEFAULT_DM_PERMISSIONS: Permissions = new Permissions( - Permissions.FLAGS.VIEW_CHANNEL | - Permissions.FLAGS.SEND_MESSAGES | - Permissions.FLAGS.STREAM | - Permissions.FLAGS.ADD_REACTIONS | - Permissions.FLAGS.EMBED_LINKS | - Permissions.FLAGS.ATTACH_FILES | - Permissions.FLAGS.READ_MESSAGE_HISTORY | - Permissions.FLAGS.MENTION_EVERYONE | - Permissions.FLAGS.USE_EXTERNAL_EMOJIS | - Permissions.FLAGS.CONNECT | - Permissions.FLAGS.SPEAK | - Permissions.FLAGS.MANAGE_CHANNELS, - ); - static ALL: Permissions = new Permissions(Object.values(Permissions.FLAGS).reduce((total, val) => total | val, BigInt(0))); + static NONE: Permissions = new Permissions(0); + static TIMED_OUT_MASK: Permissions = new Permissions(Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.READ_MESSAGE_HISTORY); + static QUARANTINED_MASK: Permissions = new Permissions(Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.READ_MESSAGE_HISTORY | Permissions.FLAGS.CHANGE_NICKNAME); + static DEFAULT_DM_PERMISSIONS: Permissions = new Permissions( + Permissions.FLAGS.VIEW_CHANNEL | + Permissions.FLAGS.SEND_MESSAGES | + Permissions.FLAGS.STREAM | + Permissions.FLAGS.ADD_REACTIONS | + Permissions.FLAGS.EMBED_LINKS | + Permissions.FLAGS.ATTACH_FILES | + Permissions.FLAGS.READ_MESSAGE_HISTORY | + Permissions.FLAGS.MENTION_EVERYONE | + Permissions.FLAGS.USE_EXTERNAL_EMOJIS | + Permissions.FLAGS.CONNECT | + Permissions.FLAGS.SPEAK | + Permissions.FLAGS.MANAGE_CHANNELS, + ); + static ALL: Permissions = new Permissions(Object.values(Permissions.FLAGS).reduce((total, val) => total | val, BigInt(0))); } export type PermissionCache = { - channel?: Channel | undefined; - member?: Member | undefined; - guild?: Guild | undefined; - roles?: Role[] | undefined; - user_id?: string; + channel?: Channel | undefined; + member?: Member | undefined; + guild?: Guild | undefined; + roles?: Role[] | undefined; + user_id?: string; }; export async function getPermission( - user_id?: string, - guild_id?: string | Guild, - channel_id?: string | Channel, - opts: { - guild_select?: (keyof Guild)[]; - guild_relations?: string[]; - channel_select?: (keyof Channel)[]; - channel_relations?: string[]; - member_select?: (keyof Member)[]; - member_relations?: string[]; - } = {}, + user_id?: string, + guild_id?: string | Guild, + channel_id?: string | Channel, + opts: { + guild_select?: (keyof Guild)[]; + guild_relations?: string[]; + channel_select?: (keyof Channel)[]; + channel_relations?: string[]; + member_select?: (keyof Member)[]; + member_relations?: string[]; + } = {}, ) { - if (!user_id) throw new HTTPError("User not found"); - let channel: Channel | undefined; - let member: Member | undefined; - let guild: Guild | undefined; - const user = await User.findOneOrFail({ - where: { id: user_id }, - select: ["id", "flags"], - }); + if (!user_id) throw new HTTPError("User not found"); + let channel: Channel | undefined; + let member: Member | undefined; + let guild: Guild | undefined; + const user = await User.findOneOrFail({ + where: { id: user_id }, + select: ["id", "flags"], + }); - if (typeof channel_id === "string") { - channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients", ...(opts.channel_relations || [])], - select: ["id", "recipients", "permission_overwrites", "owner_id", "guild_id", ...(opts.channel_select || [])], - }); - if (channel.guild_id) guild_id = channel.guild_id; // derive guild_id from the channel - } else if (channel_id) { - channel = channel_id; - } + if (typeof channel_id === "string") { + channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients", ...(opts.channel_relations || [])], + select: ["id", "recipients", "permission_overwrites", "owner_id", "guild_id", ...(opts.channel_select || [])], + }); + if (channel.guild_id) guild_id = channel.guild_id; // derive guild_id from the channel + } else if (channel_id) { + channel = channel_id; + } - if (guild_id) { - if (typeof guild_id === "string") { - guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: ["id", "owner_id", ...(opts.guild_select || [])], - relations: opts.guild_relations, - }); - } else { - guild = guild_id; - } - if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); + if (guild_id) { + if (typeof guild_id === "string") { + guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: ["id", "owner_id", ...(opts.guild_select || [])], + relations: opts.guild_relations, + }); + } else { + guild = guild_id; + } + if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); - member = await Member.findOneOrFail({ - where: { guild_id: guild.id, id: user_id }, - relations: ["roles", ...(opts.member_relations || [])], - // select: [ - // "id", // TODO: Bug in typeorm? adding these selects breaks the query. - // "roles", - // "communication_disabled_until", - // ...(opts.member_select || []), - // ], - }); - } + member = await Member.findOneOrFail({ + where: { guild_id: guild.id, id: user_id }, + relations: ["roles", ...(opts.member_relations || [])], + // select: [ + // "id", // TODO: Bug in typeorm? adding these selects breaks the query. + // "roles", + // "communication_disabled_until", + // ...(opts.member_select || []), + // ], + }); + } - let recipient_ids = channel?.recipients?.map((x) => x.user_id); - if (!recipient_ids?.length) recipient_ids = undefined; + let recipient_ids = channel?.recipients?.map((x) => x.user_id); + if (!recipient_ids?.length) recipient_ids = undefined; - // TODO: remove guild.roles and convert recipient_ids to recipients - const permission = Permissions.finalPermission({ - user: { - id: user_id, - roles: member?.roles.map((x) => x.id) || [], - communication_disabled_until: member?.communication_disabled_until ?? null, - flags: user.flags, - }, - guild: { - id: guild?.id || "", - owner_id: guild?.owner_id || "", - roles: member?.roles || [], - }, - channel: { - overwrites: channel?.permission_overwrites, - owner_id: channel?.owner_id, - recipient_ids, - }, - }); + // TODO: remove guild.roles and convert recipient_ids to recipients + const permission = Permissions.finalPermission({ + user: { + id: user_id, + roles: member?.roles.map((x) => x.id) || [], + communication_disabled_until: member?.communication_disabled_until ?? null, + flags: user.flags, + }, + guild: { + id: guild?.id || "", + owner_id: guild?.owner_id || "", + roles: member?.roles || [], + }, + channel: { + overwrites: channel?.permission_overwrites, + owner_id: channel?.owner_id, + recipient_ids, + }, + }); - const obj = new Permissions(permission); + const obj = new Permissions(permission); - // pass cache to permission for possible future getPermission calls - obj.cache = { guild, member, channel, roles: member?.roles, user_id }; + // pass cache to permission for possible future getPermission calls + obj.cache = { guild, member, channel, roles: member?.roles, user_id }; - return obj; + return obj; } diff --git a/src/util/util/RabbitMQ.ts b/src/util/util/RabbitMQ.ts index c7830de..777b77e 100644 --- a/src/util/util/RabbitMQ.ts +++ b/src/util/util/RabbitMQ.ts @@ -20,73 +20,73 @@ import { Config } from "./Config"; export class RabbitMQ { - public static connection: ChannelModel | null = null; - public static channel: Channel | null = null; + public static connection: ChannelModel | null = null; + public static channel: Channel | null = null; - static async init() { - const host = Config.get().rabbitmq.host; - if (!host) return; - console.log(`[RabbitMQ] connect: ${host}`); - this.connection = await amqp.connect(host, { - timeout: 1000 * 60, - }); - console.log(`[RabbitMQ] connected`); + static async init() { + const host = Config.get().rabbitmq.host; + if (!host) return; + console.log(`[RabbitMQ] connect: ${host}`); + this.connection = await amqp.connect(host, { + timeout: 1000 * 60, + }); + console.log(`[RabbitMQ] connected`); - // log connection errors - this.connection.on("error", (err) => { - console.error("[RabbitMQ] Connection Error:", err); - }); + // log connection errors + this.connection.on("error", (err) => { + console.error("[RabbitMQ] Connection Error:", err); + }); - this.connection.on("close", () => { - console.error("[RabbitMQ] connection closed"); - // TODO: Add reconnection logic here if the connection crashes?? - // will be a pain since we will have to reconstruct entire state - }); + this.connection.on("close", () => { + console.error("[RabbitMQ] connection closed"); + // TODO: Add reconnection logic here if the connection crashes?? + // will be a pain since we will have to reconstruct entire state + }); - await this.getSafeChannel(); // why is this here? - } + await this.getSafeChannel(); // why is this here? + } - static async getSafeChannel(): Promise { - if (!this.connection) return Promise.reject(); - if (this.channel) return this.channel; + static async getSafeChannel(): Promise { + if (!this.connection) return Promise.reject(); + if (this.channel) return this.channel; - try { - this.channel = await this.connection.createChannel(); - console.log(`[RabbitMQ] channel created`); + try { + this.channel = await this.connection.createChannel(); + console.log(`[RabbitMQ] channel created`); - // log channel errors - this.channel.on("error", (err) => { - console.error("[RabbitMQ] Channel Error:", err); - }); + // log channel errors + this.channel.on("error", (err) => { + console.error("[RabbitMQ] Channel Error:", err); + }); - this.channel.on("close", () => { - console.log("[RabbitMQ] channel closed"); - this.channel = null; - }); + this.channel.on("close", () => { + console.log("[RabbitMQ] channel closed"); + this.channel = null; + }); - this.connection.on("error", (err) => { - console.error("[RabbitMQ] connection error, setting channel to null and reconnecting:", err); - this.channel = null; - this.connection = null; - this.init(); - }); + this.connection.on("error", (err) => { + console.error("[RabbitMQ] connection error, setting channel to null and reconnecting:", err); + this.channel = null; + this.connection = null; + this.init(); + }); - this.connection.on("close", () => { - console.log("[RabbitMQ] connection closed, setting channel to null and reconnecting"); - this.channel = null; - this.connection = null; - this.init(); - }); + this.connection.on("close", () => { + console.log("[RabbitMQ] connection closed, setting channel to null and reconnecting"); + this.channel = null; + this.connection = null; + this.init(); + }); - return this.channel; - } catch (e) { - console.error("[RabbitMQ] Failed to create channel:", e); - console.error("[RabbitMQ] Forcing reconnect!"); - this.connection = null; - this.channel = null; - await this.init(); - return await this.getSafeChannel(); - // return Promise.reject(e); - } - } + return this.channel; + } catch (e) { + console.error("[RabbitMQ] Failed to create channel:", e); + console.error("[RabbitMQ] Forcing reconnect!"); + this.connection = null; + this.channel = null; + await this.init(); + return await this.getSafeChannel(); + // return Promise.reject(e); + } + } } diff --git a/src/util/util/Random.ts b/src/util/util/Random.ts index 8e4598d..b81e17c 100644 --- a/src/util/util/Random.ts +++ b/src/util/util/Random.ts @@ -1,74 +1,74 @@ // Inspired by dotnet: https://learn.microsoft.com/en-us/dotnet/api/system.random?view=net-9.0#methods export class Random { - public static nextInt(min?: number, max?: number): number { - if (min === undefined && max === undefined) { - // Next() - return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); - } else if (max === undefined) { - // Next(Int32) - if (min! <= 0) throw new RangeError("min must be greater than 0"); - return Math.floor(Math.random() * min!); - } else { - // Next(Int32, Int32) - if (min! >= max!) throw new RangeError("min must be less than max"); - return Math.floor(Math.random() * (max! - min!)) + min!; - } - } + public static nextInt(min?: number, max?: number): number { + if (min === undefined && max === undefined) { + // Next() + return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); + } else if (max === undefined) { + // Next(Int32) + if (min! <= 0) throw new RangeError("min must be greater than 0"); + return Math.floor(Math.random() * min!); + } else { + // Next(Int32, Int32) + if (min! >= max!) throw new RangeError("min must be less than max"); + return Math.floor(Math.random() * (max! - min!)) + min!; + } + } - public static nextDouble(min?: number, max?: number): number { - if (min === undefined && max === undefined) { - // NextDouble() - return Math.random(); - } else if (max === undefined) { - // NextDouble(Double) - if (min! <= 0) throw new RangeError("min must be greater than 0"); - return Math.random() * min!; - } else { - // NextDouble(Double, Double) - if (min! >= max!) throw new RangeError("min must be less than max"); - return Math.random() * (max! - min!) + min!; - } - } + public static nextDouble(min?: number, max?: number): number { + if (min === undefined && max === undefined) { + // NextDouble() + return Math.random(); + } else if (max === undefined) { + // NextDouble(Double) + if (min! <= 0) throw new RangeError("min must be greater than 0"); + return Math.random() * min!; + } else { + // NextDouble(Double, Double) + if (min! >= max!) throw new RangeError("min must be less than max"); + return Math.random() * (max! - min!) + min!; + } + } - public static nextBytes(count: number): Uint8Array { - if (count <= 0) throw new RangeError("count must be greater than 0"); - const arr = new Uint8Array(count); - for (let i = 0; i < count; i++) { - arr[i] = Math.floor(Math.random() * 256); - } - return arr; - } + public static nextBytes(count: number): Uint8Array { + if (count <= 0) throw new RangeError("count must be greater than 0"); + const arr = new Uint8Array(count); + for (let i = 0; i < count; i++) { + arr[i] = Math.floor(Math.random() * 256); + } + return arr; + } - public static nextBytesArray(count: number) { - if (count <= 0) throw new RangeError("count must be greater than 0"); - const arr = []; - for (let i = 0; i < count; i++) { - arr.push(Math.floor(Math.random() * 256)); - } - return arr; - } + public static nextBytesArray(count: number) { + if (count <= 0) throw new RangeError("count must be greater than 0"); + const arr = []; + for (let i = 0; i < count; i++) { + arr.push(Math.floor(Math.random() * 256)); + } + return arr; + } - public static getItems(items: T[], count: number): T[] { - if (count <= 0) throw new RangeError("count must be greater than 0"); - if (count >= items.length) return this.shuffle(items); - const usedIndices = new Set(); - const result: T[] = []; - while (result.length < count && usedIndices.size < items.length) { - const index = Math.floor(Math.random() * items.length); - if (!usedIndices.has(index)) { - usedIndices.add(index); - result.push(items[index]); - } - } - return result; - } + public static getItems(items: T[], count: number): T[] { + if (count <= 0) throw new RangeError("count must be greater than 0"); + if (count >= items.length) return this.shuffle(items); + const usedIndices = new Set(); + const result: T[] = []; + while (result.length < count && usedIndices.size < items.length) { + const index = Math.floor(Math.random() * items.length); + if (!usedIndices.has(index)) { + usedIndices.add(index); + result.push(items[index]); + } + } + return result; + } - public static shuffle(items: T[]): T[] { - const array = [...items]; - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } - return array; - } + public static shuffle(items: T[]): T[] { + const array = [...items]; + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; + } } diff --git a/src/util/util/Rights.ts b/src/util/util/Rights.ts index 644471f..6b90f74 100644 --- a/src/util/util/Rights.ts +++ b/src/util/util/Rights.ts @@ -27,92 +27,92 @@ // TODO: just like roles for members, users should have priviliges which combine multiple rights into one and make it easy to assign export class Rights extends BitField { - constructor(bits: BitFieldResolvable = 0) { - super(bits); - if (this.bitfield & Rights.FLAGS.OPERATOR) { - this.bitfield = ALL_RIGHTS; - } - } + constructor(bits: BitFieldResolvable = 0) { + super(bits); + if (this.bitfield & Rights.FLAGS.OPERATOR) { + this.bitfield = ALL_RIGHTS; + } + } - static FLAGS = { - OPERATOR: BitFlag(0), // has all rights - MANAGE_APPLICATIONS: BitFlag(1), - MANAGE_GUILDS: BitFlag(2), // Manage all guilds instance-wide - MANAGE_MESSAGES: BitFlag(3), // Can't see other messages but delete/edit them in channels that they can see - MANAGE_RATE_LIMITS: BitFlag(4), - MANAGE_ROUTING: BitFlag(5), // can create custom message routes to any channel/guild - MANAGE_TICKETS: BitFlag(6), // can respond to and resolve support tickets - MANAGE_USERS: BitFlag(7), - ADD_MEMBERS: BitFlag(8), // can manually add any members in their guilds - BYPASS_RATE_LIMITS: BitFlag(9), - CREATE_APPLICATIONS: BitFlag(10), - CREATE_CHANNELS: BitFlag(11), // can create guild channels or threads in the guilds that they have permission - CREATE_DMS: BitFlag(12), - CREATE_DM_GROUPS: BitFlag(13), // can create group DMs or custom orphan channels - CREATE_GUILDS: BitFlag(14), - CREATE_INVITES: BitFlag(15), // can create mass invites in the guilds that they have CREATE_INSTANT_INVITE - CREATE_ROLES: BitFlag(16), - CREATE_TEMPLATES: BitFlag(17), - CREATE_WEBHOOKS: BitFlag(18), - JOIN_GUILDS: BitFlag(19), - PIN_MESSAGES: BitFlag(20), - SELF_ADD_REACTIONS: BitFlag(21), - SELF_DELETE_MESSAGES: BitFlag(22), - SELF_EDIT_MESSAGES: BitFlag(23), - SELF_EDIT_NAME: BitFlag(24), - SEND_MESSAGES: BitFlag(25), - USE_ACTIVITIES: BitFlag(26), // use (game) activities in voice channels (e.g. Watch together) - USE_VIDEO: BitFlag(27), - USE_VOICE: BitFlag(28), - INVITE_USERS: BitFlag(29), // can create user-specific invites in the guilds that they have INVITE_USERS - SELF_DELETE_DISABLE: BitFlag(30), // can disable/delete own account - DEBTABLE: BitFlag(31), // can use pay-to-use features - CREDITABLE: BitFlag(32), // can receive money from monetisation related features - KICK_BAN_MEMBERS: BitFlag(33), - // can kick or ban guild or group DM members in the guilds/groups that they have KICK_MEMBERS, or BAN_MEMBERS - SELF_LEAVE_GROUPS: BitFlag(34), - // can leave the guilds or group DMs that they joined on their own (one can always leave a guild or group DMs they have been force-added) - PRESENCE: BitFlag(35), - // inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user - SELF_ADD_DISCOVERABLE: BitFlag(36), // can mark discoverable guilds that they have permissions to mark as discoverable - MANAGE_GUILD_DIRECTORY: BitFlag(37), // can change anything in the primary guild directory - POGGERS: BitFlag(38), // can send confetti, screenshake, random user mention (@someone) - USE_ACHIEVEMENTS: BitFlag(39), // can use achievements and cheers - INITIATE_INTERACTIONS: BitFlag(40), // can initiate interactions - RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions - SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events - USE_MASS_INVITES: BitFlag(43), // added per @xnacly's request - can accept mass invites - ACCEPT_INVITES: BitFlag(44), // added per @xnacly's request - can accept user-specific invites and DM requests - SELF_EDIT_FLAGS: BitFlag(45), // can modify own flags - EDIT_FLAGS: BitFlag(46), // can set others' flags - MANAGE_GROUPS: BitFlag(47), // can manage others' groups - VIEW_SERVER_STATS: BitFlag(48), // added per @chrischrome's request - can view server stats) - RESEND_VERIFICATION_EMAIL: BitFlag(49), // can resend verification emails (/auth/verify/resend) - CREATE_REGISTRATION_TOKENS: BitFlag(50), // can create registration tokens (/auth/generate-registration-tokens) - }; + static FLAGS = { + OPERATOR: BitFlag(0), // has all rights + MANAGE_APPLICATIONS: BitFlag(1), + MANAGE_GUILDS: BitFlag(2), // Manage all guilds instance-wide + MANAGE_MESSAGES: BitFlag(3), // Can't see other messages but delete/edit them in channels that they can see + MANAGE_RATE_LIMITS: BitFlag(4), + MANAGE_ROUTING: BitFlag(5), // can create custom message routes to any channel/guild + MANAGE_TICKETS: BitFlag(6), // can respond to and resolve support tickets + MANAGE_USERS: BitFlag(7), + ADD_MEMBERS: BitFlag(8), // can manually add any members in their guilds + BYPASS_RATE_LIMITS: BitFlag(9), + CREATE_APPLICATIONS: BitFlag(10), + CREATE_CHANNELS: BitFlag(11), // can create guild channels or threads in the guilds that they have permission + CREATE_DMS: BitFlag(12), + CREATE_DM_GROUPS: BitFlag(13), // can create group DMs or custom orphan channels + CREATE_GUILDS: BitFlag(14), + CREATE_INVITES: BitFlag(15), // can create mass invites in the guilds that they have CREATE_INSTANT_INVITE + CREATE_ROLES: BitFlag(16), + CREATE_TEMPLATES: BitFlag(17), + CREATE_WEBHOOKS: BitFlag(18), + JOIN_GUILDS: BitFlag(19), + PIN_MESSAGES: BitFlag(20), + SELF_ADD_REACTIONS: BitFlag(21), + SELF_DELETE_MESSAGES: BitFlag(22), + SELF_EDIT_MESSAGES: BitFlag(23), + SELF_EDIT_NAME: BitFlag(24), + SEND_MESSAGES: BitFlag(25), + USE_ACTIVITIES: BitFlag(26), // use (game) activities in voice channels (e.g. Watch together) + USE_VIDEO: BitFlag(27), + USE_VOICE: BitFlag(28), + INVITE_USERS: BitFlag(29), // can create user-specific invites in the guilds that they have INVITE_USERS + SELF_DELETE_DISABLE: BitFlag(30), // can disable/delete own account + DEBTABLE: BitFlag(31), // can use pay-to-use features + CREDITABLE: BitFlag(32), // can receive money from monetisation related features + KICK_BAN_MEMBERS: BitFlag(33), + // can kick or ban guild or group DM members in the guilds/groups that they have KICK_MEMBERS, or BAN_MEMBERS + SELF_LEAVE_GROUPS: BitFlag(34), + // can leave the guilds or group DMs that they joined on their own (one can always leave a guild or group DMs they have been force-added) + PRESENCE: BitFlag(35), + // inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user + SELF_ADD_DISCOVERABLE: BitFlag(36), // can mark discoverable guilds that they have permissions to mark as discoverable + MANAGE_GUILD_DIRECTORY: BitFlag(37), // can change anything in the primary guild directory + POGGERS: BitFlag(38), // can send confetti, screenshake, random user mention (@someone) + USE_ACHIEVEMENTS: BitFlag(39), // can use achievements and cheers + INITIATE_INTERACTIONS: BitFlag(40), // can initiate interactions + RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions + SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events + USE_MASS_INVITES: BitFlag(43), // added per @xnacly's request - can accept mass invites + ACCEPT_INVITES: BitFlag(44), // added per @xnacly's request - can accept user-specific invites and DM requests + SELF_EDIT_FLAGS: BitFlag(45), // can modify own flags + EDIT_FLAGS: BitFlag(46), // can set others' flags + MANAGE_GROUPS: BitFlag(47), // can manage others' groups + VIEW_SERVER_STATS: BitFlag(48), // added per @chrischrome's request - can view server stats) + RESEND_VERIFICATION_EMAIL: BitFlag(49), // can resend verification emails (/auth/verify/resend) + CREATE_REGISTRATION_TOKENS: BitFlag(50), // can create registration tokens (/auth/generate-registration-tokens) + }; - any(permission: RightResolvable, checkOperator = true) { - return (checkOperator && super.any(Rights.FLAGS.OPERATOR)) || super.any(permission); - } + any(permission: RightResolvable, checkOperator = true) { + return (checkOperator && super.any(Rights.FLAGS.OPERATOR)) || super.any(permission); + } - has(permission: RightResolvable, checkOperator = true) { - return (checkOperator && super.has(Rights.FLAGS.OPERATOR)) || super.has(permission); - } + has(permission: RightResolvable, checkOperator = true) { + return (checkOperator && super.has(Rights.FLAGS.OPERATOR)) || super.has(permission); + } - hasThrow(permission: RightResolvable) { - if (this.has(permission)) return true; - throw new HTTPError(`You are missing the following rights ${permission}`, 403); - } + hasThrow(permission: RightResolvable) { + if (this.has(permission)) return true; + throw new HTTPError(`You are missing the following rights ${permission}`, 403); + } } const ALL_RIGHTS = Object.values(Rights.FLAGS).reduce((total, val) => total | val, BigInt(0)); export async function getRights( - user_id: string, - /**, opts: { + user_id: string, + /**, opts: { in_behalf?: (keyof User)[]; } = {} **/ ) { - const user = await User.findOneOrFail({ where: { id: user_id } }); - return new Rights(user.rights); + const user = await User.findOneOrFail({ where: { id: user_id } }); + return new Rights(user.rights); } diff --git a/src/util/util/Snowflake.ts b/src/util/util/Snowflake.ts index 8af567c..ee6036b 100644 --- a/src/util/util/Snowflake.ts +++ b/src/util/util/Snowflake.ts @@ -12,121 +12,121 @@ * A container for useful snowflake-related methods. */ export class Snowflake { - static readonly EPOCH = 1420070400000; - static INCREMENT = 0n; // max 4095 - static processId = BigInt(process.pid % 31); // max 31 - static workerId = BigInt((cluster.worker?.id || 0) % 31); // max 31 + static readonly EPOCH = 1420070400000; + static INCREMENT = 0n; // max 4095 + static processId = BigInt(process.pid % 31); // max 31 + static workerId = BigInt((cluster.worker?.id || 0) % 31); // max 31 - constructor() { - throw new Error(`The ${this.constructor.name} class may not be instantiated.`); - } + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } - /** - * A Twitter-like snowflake, except the epoch is 2015-01-01T00:00:00.000Z - * ``` - * If we have a snowflake '266241948824764416' we can represent it as binary: - * - * 64 22 17 12 0 - * 000000111011000111100001101001000101000000 00001 00000 000000000000 - * number of ms since Discord epoch worker pid increment - * ``` - * @typedef {string} Snowflake - */ + /** + * A Twitter-like snowflake, except the epoch is 2015-01-01T00:00:00.000Z + * ``` + * If we have a snowflake '266241948824764416' we can represent it as binary: + * + * 64 22 17 12 0 + * 000000111011000111100001101001000101000000 00001 00000 000000000000 + * number of ms since Discord epoch worker pid increment + * ``` + * @typedef {string} Snowflake + */ - /** - * Transforms a snowflake from a decimal string to a bit string. - * @param {Snowflake} num Snowflake to be transformed - * @returns {string} - * @private - */ - static idToBinary(num) { - let bin = ""; - let high = parseInt(num.slice(0, -10)) || 0; - let low = parseInt(num.slice(-10)); - while (low > 0 || high > 0) { - bin = String(low & 1) + bin; - low = Math.floor(low / 2); - if (high > 0) { - low += 5000000000 * (high % 2); - high = Math.floor(high / 2); - } - } - return bin; - } + /** + * Transforms a snowflake from a decimal string to a bit string. + * @param {Snowflake} num Snowflake to be transformed + * @returns {string} + * @private + */ + static idToBinary(num) { + let bin = ""; + let high = parseInt(num.slice(0, -10)) || 0; + let low = parseInt(num.slice(-10)); + while (low > 0 || high > 0) { + bin = String(low & 1) + bin; + low = Math.floor(low / 2); + if (high > 0) { + low += 5000000000 * (high % 2); + high = Math.floor(high / 2); + } + } + return bin; + } - /** - * Transforms a snowflake from a bit string to a decimal string. - * @param {string} num Bit string to be transformed - * @returns {Snowflake} - * @private - */ - static binaryToID(num) { - let dec = ""; + /** + * Transforms a snowflake from a bit string to a decimal string. + * @param {string} num Bit string to be transformed + * @returns {Snowflake} + * @private + */ + static binaryToID(num) { + let dec = ""; - while (num.length > 50) { - const high = parseInt(num.slice(0, -32), 2); - const low = parseInt((high % 10).toString(2) + num.slice(-32), 2); + while (num.length > 50) { + const high = parseInt(num.slice(0, -32), 2); + const low = parseInt((high % 10).toString(2) + num.slice(-32), 2); - dec = (low % 10).toString() + dec; - num = - Math.floor(high / 10).toString(2) + - Math.floor(low / 10) - .toString(2) - .padStart(32, "0"); - } + dec = (low % 10).toString() + dec; + num = + Math.floor(high / 10).toString(2) + + Math.floor(low / 10) + .toString(2) + .padStart(32, "0"); + } - num = parseInt(num, 2); - while (num > 0) { - dec = (num % 10).toString() + dec; - num = Math.floor(num / 10); - } + num = parseInt(num, 2); + while (num > 0) { + dec = (num % 10).toString() + dec; + num = Math.floor(num / 10); + } - return dec; - } + return dec; + } - static generateWorkerProcess() { - // worker process - returns a number - const time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); - const worker = Snowflake.workerId << 17n; - const process = Snowflake.processId << 12n; - const increment = Snowflake.INCREMENT++; - return BigInt(time | worker | process | increment); - } + static generateWorkerProcess() { + // worker process - returns a number + const time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); + const worker = Snowflake.workerId << 17n; + const process = Snowflake.processId << 12n; + const increment = Snowflake.INCREMENT++; + return BigInt(time | worker | process | increment); + } - static generate() { - return Snowflake.generateWorkerProcess().toString(); - } - /** - * A deconstructed snowflake. - * @typedef {Object} DeconstructedSnowflake - * @property {number} timestamp Timestamp the snowflake was created - * @property {Date} date Date the snowflake was created - * @property {number} workerID Worker ID in the snowflake - * @property {number} processID Process ID in the snowflake - * @property {number} increment Increment in the snowflake - * @property {string} binary Binary representation of the snowflake - */ + static generate() { + return Snowflake.generateWorkerProcess().toString(); + } + /** + * A deconstructed snowflake. + * @typedef {Object} DeconstructedSnowflake + * @property {number} timestamp Timestamp the snowflake was created + * @property {Date} date Date the snowflake was created + * @property {number} workerID Worker ID in the snowflake + * @property {number} processID Process ID in the snowflake + * @property {number} increment Increment in the snowflake + * @property {string} binary Binary representation of the snowflake + */ - /** - * Deconstructs a Discord snowflake. - * @param {Snowflake} snowflake Snowflake to deconstruct - * @returns {DeconstructedSnowflake} Deconstructed snowflake - */ - static deconstruct(snowflake) { - const BINARY = Snowflake.idToBinary(snowflake).toString(2).padStart(64, "0"); - const res = { - timestamp: parseInt(BINARY.substring(0, 42), 2) + Snowflake.EPOCH, - workerID: parseInt(BINARY.substring(42, 47), 2), - processID: parseInt(BINARY.substring(47, 52), 2), - increment: parseInt(BINARY.substring(52, 64), 2), - binary: BINARY, - }; - Object.defineProperty(res, "date", { - get: function get() { - return new Date(this.timestamp); - }, - enumerable: true, - }); - return res; - } + /** + * Deconstructs a Discord snowflake. + * @param {Snowflake} snowflake Snowflake to deconstruct + * @returns {DeconstructedSnowflake} Deconstructed snowflake + */ + static deconstruct(snowflake) { + const BINARY = Snowflake.idToBinary(snowflake).toString(2).padStart(64, "0"); + const res = { + timestamp: parseInt(BINARY.substring(0, 42), 2) + Snowflake.EPOCH, + workerID: parseInt(BINARY.substring(42, 47), 2), + processID: parseInt(BINARY.substring(47, 52), 2), + increment: parseInt(BINARY.substring(52, 64), 2), + binary: BINARY, + }; + Object.defineProperty(res, "date", { + get: function get() { + return new Date(this.timestamp); + }, + enumerable: true, + }); + return res; + } } diff --git a/src/util/util/Stopwatch.test.ts b/src/util/util/Stopwatch.test.ts index 7d3d68e..f754f2e 100644 --- a/src/util/util/Stopwatch.test.ts +++ b/src/util/util/Stopwatch.test.ts @@ -3,50 +3,50 @@ import { Stopwatch, timePromise } from "./Stopwatch"; test("Stopwatch should be able to be initialised", () => { - const sw = new Stopwatch(); - assert.equal(sw != null, true); + const sw = new Stopwatch(); + assert.equal(sw != null, true); }); test("Stopwatch should measure elapsed time", async () => { - const sw = Stopwatch.startNew(); - await new Promise((resolve) => setTimeout(resolve, 101)); - sw.stop(); - const elapsed = sw.elapsed(); - assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); + const sw = Stopwatch.startNew(); + await new Promise((resolve) => setTimeout(resolve, 101)); + sw.stop(); + const elapsed = sw.elapsed(); + assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); }); test("Stopwatch should reset correctly", async () => { - const sw = Stopwatch.startNew(); - await new Promise((resolve) => setTimeout(resolve, 101)); - sw.stop(); - let elapsed = sw.elapsed(); - assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); + const sw = Stopwatch.startNew(); + await new Promise((resolve) => setTimeout(resolve, 101)); + sw.stop(); + let elapsed = sw.elapsed(); + assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); - sw.reset(); - await new Promise((resolve) => setTimeout(resolve, 50)); - sw.stop(); - elapsed = sw.elapsed(); - assert(elapsed.totalMilliseconds >= 50 && elapsed.totalMilliseconds < 100, `Elapsed time after reset was ${elapsed.totalMilliseconds} ms`); + sw.reset(); + await new Promise((resolve) => setTimeout(resolve, 50)); + sw.stop(); + elapsed = sw.elapsed(); + assert(elapsed.totalMilliseconds >= 50 && elapsed.totalMilliseconds < 100, `Elapsed time after reset was ${elapsed.totalMilliseconds} ms`); }); test("Stopwatch getElapsedAndReset should work correctly", async () => { - const sw = Stopwatch.startNew(); - await new Promise((resolve) => setTimeout(resolve, 101)); - sw.stop(); - let elapsed = sw.getElapsedAndReset(); - assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); + const sw = Stopwatch.startNew(); + await new Promise((resolve) => setTimeout(resolve, 101)); + sw.stop(); + let elapsed = sw.getElapsedAndReset(); + assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); - await new Promise((resolve) => setTimeout(resolve, 50)); - sw.stop(); - elapsed = sw.elapsed(); - assert(elapsed.totalMilliseconds >= 50 && elapsed.totalMilliseconds < 100, `Elapsed time after getElapsedAndReset was ${elapsed.totalMilliseconds} ms`); + await new Promise((resolve) => setTimeout(resolve, 50)); + sw.stop(); + elapsed = sw.elapsed(); + assert(elapsed.totalMilliseconds >= 50 && elapsed.totalMilliseconds < 100, `Elapsed time after getElapsedAndReset was ${elapsed.totalMilliseconds} ms`); }); test("timePromise should measure promise execution time", async () => { - const { result, elapsed } = await timePromise(async () => { - await new Promise((resolve) => setTimeout(resolve, 101)); - return 42; - }); - assert.equal(result, 42); - assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); + const { result, elapsed } = await timePromise(async () => { + await new Promise((resolve) => setTimeout(resolve, 101)); + return 42; + }); + assert.equal(result, 42); + assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); }); diff --git a/src/util/util/Stopwatch.ts b/src/util/util/Stopwatch.ts index aaaf2b2..a52dcf9 100644 --- a/src/util/util/Stopwatch.ts +++ b/src/util/util/Stopwatch.ts @@ -21,43 +21,43 @@ import { ElapsedTime } from "./ElapsedTime"; export class Stopwatch { - private startTime: bigint; - private endTime: bigint | null = null; + private startTime: bigint; + private endTime: bigint | null = null; - static startNew(): Stopwatch { - const stopwatch = new Stopwatch(); - stopwatch.start(); - return stopwatch; - } + static startNew(): Stopwatch { + const stopwatch = new Stopwatch(); + stopwatch.start(); + return stopwatch; + } - start(): void { - this.startTime = process.hrtime.bigint(); - this.endTime = null; - } + start(): void { + this.startTime = process.hrtime.bigint(); + this.endTime = null; + } - reset(): void { - this.startTime = process.hrtime.bigint(); - this.endTime = null; - } + reset(): void { + this.startTime = process.hrtime.bigint(); + this.endTime = null; + } - stop(): void { - this.endTime = process.hrtime.bigint(); - } + stop(): void { + this.endTime = process.hrtime.bigint(); + } - elapsed(): ElapsedTime { - return new ElapsedTime((this.endTime ?? process.hrtime.bigint()) - this.startTime); - } + elapsed(): ElapsedTime { + return new ElapsedTime((this.endTime ?? process.hrtime.bigint()) - this.startTime); + } - getElapsedAndReset(): ElapsedTime { - const elapsed = this.elapsed(); - this.reset(); - return elapsed; - } + getElapsedAndReset(): ElapsedTime { + const elapsed = this.elapsed(); + this.reset(); + return elapsed; + } } export async function timePromise(fn: () => Promise): Promise<{ result: T; elapsed: ElapsedTime }> { - const stopwatch = Stopwatch.startNew(); - const result = await fn(); - const elapsed = stopwatch.elapsed(); - return { result, elapsed }; + const stopwatch = Stopwatch.startNew(); + const result = await fn(); + const elapsed = stopwatch.elapsed(); + return { result, elapsed }; } diff --git a/src/util/util/String.ts b/src/util/util/String.ts index f79e73f..d0e7570 100644 --- a/src/util/util/String.ts +++ b/src/util/util/String.ts @@ -19,8 +19,8 @@ import { SPECIAL_CHAR } from "./Regex"; export function trimSpecial(str?: string): string { - if (!str) return ""; - return str.replace(SPECIAL_CHAR, "").trim(); + if (!str) return ""; + return str.replace(SPECIAL_CHAR, "").trim(); } /** @@ -29,18 +29,18 @@ * @returns The capitalized string. */ export function capitalize(str: string): string { - if (!str) return ""; - return str.charAt(0).toUpperCase() + str.slice(1); + if (!str) return ""; + return str.charAt(0).toUpperCase() + str.slice(1); } export function centerString(str: string, len: number): string { - const pad = len - str.length; - const padLeft = Math.floor(pad / 2) + str.length; - return str.padStart(padLeft).padEnd(len); + const pad = len - str.length; + const padLeft = Math.floor(pad / 2) + str.length; + return str.padStart(padLeft).padEnd(len); } export function stringGlobToRegexp(str: string, flags?: string): RegExp { - // Convert simple wildcard patterns to regex - const escaped = str.replace(".", "\\.").replace("?", ".").replace("*", ".*"); - return new RegExp(escaped, flags); + // Convert simple wildcard patterns to regex + const escaped = str.replace(".", "\\.").replace("?", ".").replace("*", ".*"); + return new RegExp(escaped, flags); } diff --git a/src/util/util/Timespan.test.ts b/src/util/util/Timespan.test.ts index 8a18c1b..107d6e5 100644 --- a/src/util/util/Timespan.test.ts +++ b/src/util/util/Timespan.test.ts @@ -3,118 +3,118 @@ import { TimeSpan } from "./Timespan"; test("TimeSpan should be able to be initialised", () => { - const db = new TimeSpan(); - assert.equal(db != null, true); + const db = new TimeSpan(); + assert.equal(db != null, true); }); test("TimeSpan should be able to be initialised with start and end", () => { - const now = Date.now(); - const later = now + 5000; - const ts = new TimeSpan(now, later); - assert.equal(ts.start, now); - assert.equal(ts.end, later); + const now = Date.now(); + const later = now + 5000; + const ts = new TimeSpan(now, later); + assert.equal(ts.start, now); + assert.equal(ts.end, later); }); test("TimeSpan should be able to be initialised with start and end (fromDates static method)", () => { - const now = Date.now(); - const later = now + 5000; - const ts = TimeSpan.fromDates(now, later); - assert.equal(ts.start, now); - assert.equal(ts.end, later); + const now = Date.now(); + const later = now + 5000; + const ts = TimeSpan.fromDates(now, later); + assert.equal(ts.start, now); + assert.equal(ts.end, later); }); test("TimeSpan should throw error if start is greater than end", () => { - assert.throws(() => { - new TimeSpan(2000, 1000); - }, /Start time must be less than or equal to end time./); + assert.throws(() => { + new TimeSpan(2000, 1000); + }, /Start time must be less than or equal to end time./); }); test("TimeSpan should be able to return zero", () => { - const ts = new TimeSpan(); - assert.equal(ts.totalMillis, 0); + const ts = new TimeSpan(); + assert.equal(ts.totalMillis, 0); }); test("TimeSpan should be able to return timespan from milliseconds", () => { - const ts = TimeSpan.fromMillis(1000); - assert.equal(ts.totalMillis, 1000); - assert.equal(ts.totalSeconds, 1); + const ts = TimeSpan.fromMillis(1000); + assert.equal(ts.totalMillis, 1000); + assert.equal(ts.totalSeconds, 1); }); test("TimeSpan should be able to return timespan from seconds", () => { - const ts = TimeSpan.fromSeconds(60); - assert.equal(ts.totalMillis, 60000); - assert.equal(ts.totalSeconds, 60); - assert.equal(ts.totalMinutes, 1); - assert.equal(ts.minutes, 1); - assert.equal(ts.hours, 0); - assert.equal(ts.days, 0); + const ts = TimeSpan.fromSeconds(60); + assert.equal(ts.totalMillis, 60000); + assert.equal(ts.totalSeconds, 60); + assert.equal(ts.totalMinutes, 1); + assert.equal(ts.minutes, 1); + assert.equal(ts.hours, 0); + assert.equal(ts.days, 0); }); test("TimeSpan should be pure", () => { - const count = 10; - const timestamps = []; - for (let i = 0; i < count; i++) { - timestamps.push(TimeSpan.fromMillis(8972347984)); - for (const ts2 of timestamps) { - assert.equal(ts2.totalMillis, 8972347984); - assert.equal(ts2.totalSeconds, 8972347); - assert.equal(ts2.totalMinutes, 149539); - assert.equal(ts2.totalHours, 2492); - assert.equal(ts2.totalDays, 103); - assert.equal(ts2.totalWeeks, 14); - assert.equal(ts2.totalMonths, 3); - assert.equal(ts2.totalYears, 0); + const count = 10; + const timestamps = []; + for (let i = 0; i < count; i++) { + timestamps.push(TimeSpan.fromMillis(8972347984)); + for (const ts2 of timestamps) { + assert.equal(ts2.totalMillis, 8972347984); + assert.equal(ts2.totalSeconds, 8972347); + assert.equal(ts2.totalMinutes, 149539); + assert.equal(ts2.totalHours, 2492); + assert.equal(ts2.totalDays, 103); + assert.equal(ts2.totalWeeks, 14); + assert.equal(ts2.totalMonths, 3); + assert.equal(ts2.totalYears, 0); - assert.equal(ts2.millis, 984); - assert.equal(ts2.seconds, 7); - assert.equal(ts2.minutes, 19); - assert.equal(ts2.hours, 20); - assert.equal(ts2.days, 12); - assert.equal(ts2.weekDays, 5); - assert.equal(ts2.weeks, 1); - assert.equal(ts2.months, 3); - assert.equal(ts2.years, 0); - } - } + assert.equal(ts2.millis, 984); + assert.equal(ts2.seconds, 7); + assert.equal(ts2.minutes, 19); + assert.equal(ts2.hours, 20); + assert.equal(ts2.days, 12); + assert.equal(ts2.weekDays, 5); + assert.equal(ts2.weeks, 1); + assert.equal(ts2.months, 3); + assert.equal(ts2.years, 0); + } + } }); test("TimeSpan should be able to stringify", () => { - const ts = TimeSpan.fromMillis(8972347984); - assert.equal(ts.toString(), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); - assert.equal(ts.toString(true), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); - assert.equal(ts.toString(true, false), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds"); - assert.equal(ts.toString(false), "3 months, 12 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); - assert.equal(ts.toString(false, false), "3 months, 12 days, 20 hours, 19 minutes, 7 seconds"); + const ts = TimeSpan.fromMillis(8972347984); + assert.equal(ts.toString(), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); + assert.equal(ts.toString(true), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); + assert.equal(ts.toString(true, false), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds"); + assert.equal(ts.toString(false), "3 months, 12 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); + assert.equal(ts.toString(false, false), "3 months, 12 days, 20 hours, 19 minutes, 7 seconds"); }); test("TimeSpan should be able to shortStringify", () => { - const ts = TimeSpan.fromMillis(8972347984); - assert.equal(ts.toShortString(), "3mo1w5d20h19m7s984ms"); - assert.equal(ts.toShortString(true), "3mo1w5d20h19m7s984ms"); - assert.equal(ts.toShortString(true, false), "3mo1w5d20h19m7s"); - assert.equal(ts.toShortString(false), "3mo12d20h19m7s984ms"); - assert.equal(ts.toShortString(false, false), "3mo12d20h19m7s"); + const ts = TimeSpan.fromMillis(8972347984); + assert.equal(ts.toShortString(), "3mo1w5d20h19m7s984ms"); + assert.equal(ts.toShortString(true), "3mo1w5d20h19m7s984ms"); + assert.equal(ts.toShortString(true, false), "3mo1w5d20h19m7s"); + assert.equal(ts.toShortString(false), "3mo12d20h19m7s984ms"); + assert.equal(ts.toShortString(false, false), "3mo12d20h19m7s"); }); test("TimeSpan should be able to shortStringify with spaces", () => { - const ts = TimeSpan.fromMillis(8972347984); - assert.equal(ts.toShortString(undefined, undefined, true), "3mo 1w 5d 20h 19m 7s 984ms"); - assert.equal(ts.toShortString(true, undefined, true), "3mo 1w 5d 20h 19m 7s 984ms"); - assert.equal(ts.toShortString(true, false, true), "3mo 1w 5d 20h 19m 7s"); - assert.equal(ts.toShortString(false, undefined, true), "3mo 12d 20h 19m 7s 984ms"); - assert.equal(ts.toShortString(false, false, true), "3mo 12d 20h 19m 7s"); + const ts = TimeSpan.fromMillis(8972347984); + assert.equal(ts.toShortString(undefined, undefined, true), "3mo 1w 5d 20h 19m 7s 984ms"); + assert.equal(ts.toShortString(true, undefined, true), "3mo 1w 5d 20h 19m 7s 984ms"); + assert.equal(ts.toShortString(true, false, true), "3mo 1w 5d 20h 19m 7s"); + assert.equal(ts.toShortString(false, undefined, true), "3mo 12d 20h 19m 7s 984ms"); + assert.equal(ts.toShortString(false, false, true), "3mo 12d 20h 19m 7s"); }); test("TimeSpan should be able to return start date", () => { - const now = Date.now(); - const later = now + 5000; - const ts = new TimeSpan(now, later); - assert.equal(ts.startDate.getTime(), now); + const now = Date.now(); + const later = now + 5000; + const ts = new TimeSpan(now, later); + assert.equal(ts.startDate.getTime(), now); }); test("TimeSpan should be able to return end date", () => { - const now = Date.now(); - const later = now + 5000; - const ts = new TimeSpan(now, later); - assert.equal(ts.endDate.getTime(), later); + const now = Date.now(); + const later = now + 5000; + const ts = new TimeSpan(now, later); + assert.equal(ts.endDate.getTime(), later); }); diff --git a/src/util/util/Timespan.ts b/src/util/util/Timespan.ts index 323056c..ad0c1d1 100644 --- a/src/util/util/Timespan.ts +++ b/src/util/util/Timespan.ts @@ -2,129 +2,129 @@ * Represents a timespan with a start and end time. */ export class TimeSpan { - public readonly start: number; - public readonly end: number; - // constructors - constructor(start = Date.now(), end = Date.now()) { - if (start > end) { - throw new Error("Start time must be less than or equal to end time."); - } - this.start = start; - this.end = end; - } + public readonly start: number; + public readonly end: number; + // constructors + constructor(start = Date.now(), end = Date.now()) { + if (start > end) { + throw new Error("Start time must be less than or equal to end time."); + } + this.start = start; + this.end = end; + } - static fromDates(startDate: number, endDate: number) { - return new TimeSpan(startDate, endDate); - } + static fromDates(startDate: number, endDate: number) { + return new TimeSpan(startDate, endDate); + } - static fromMillis(millis: number) { - return new TimeSpan(0, millis); - } + static fromMillis(millis: number) { + return new TimeSpan(0, millis); + } - static fromSeconds(seconds: number) { - return TimeSpan.fromMillis(seconds * 1000); - } + static fromSeconds(seconds: number) { + return TimeSpan.fromMillis(seconds * 1000); + } - // methods - get totalMillis() { - return this.end - this.start; - } + // methods + get totalMillis() { + return this.end - this.start; + } - get millis() { - return Math.floor(this.totalMillis % 1000); - } + get millis() { + return Math.floor(this.totalMillis % 1000); + } - get totalSeconds() { - return Math.floor(this.totalMillis / 1000); - } + get totalSeconds() { + return Math.floor(this.totalMillis / 1000); + } - get seconds() { - return Math.floor((this.totalMillis / 1000) % 60); - } + get seconds() { + return Math.floor((this.totalMillis / 1000) % 60); + } - get totalMinutes() { - return Math.floor(this.totalMillis / 1000 / 60); - } + get totalMinutes() { + return Math.floor(this.totalMillis / 1000 / 60); + } - get minutes() { - return Math.floor((this.totalMillis / 1000 / 60) % 60); - } + get minutes() { + return Math.floor((this.totalMillis / 1000 / 60) % 60); + } - get totalHours() { - return Math.floor(this.totalMillis / 1000 / 60 / 60); - } + get totalHours() { + return Math.floor(this.totalMillis / 1000 / 60 / 60); + } - get hours() { - return Math.floor((this.totalMillis / 1000 / 60 / 60) % 24); - } + get hours() { + return Math.floor((this.totalMillis / 1000 / 60 / 60) % 24); + } - get totalDays() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24); - } + get totalDays() { + return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24); + } - get days() { - return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24) % 30.44); // Average days in a month - } + get days() { + return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24) % 30.44); // Average days in a month + } - get weekDays() { - return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24) % 7); - } + get weekDays() { + return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24) % 7); + } - get totalWeeks() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 7); - } + get totalWeeks() { + return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 7); + } - get weeks() { - return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24 / 7) % 4.345); // Average weeks in a month - } + get weeks() { + return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24 / 7) % 4.345); // Average weeks in a month + } - get totalMonths() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 30.44); // Average days in a month - } + get totalMonths() { + return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 30.44); // Average days in a month + } - get months() { - return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24 / 30.44) % 12); // Average days in a month - } + get months() { + return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24 / 30.44) % 12); // Average days in a month + } - get totalYears() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year - } + get totalYears() { + return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year + } - get years() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year - } + get years() { + return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year + } - toString(includeWeeks = true, includeMillis = true) { - const parts = []; - if (this.totalYears >= 1) parts.push(`${this.totalYears} years`); - if (this.totalMonths >= 1) parts.push(`${this.months} months`); - if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks} weeks`); - if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days} days`); - if (this.totalHours >= 1) parts.push(`${this.hours} hours`); - if (this.totalMinutes >= 1) parts.push(`${this.minutes} minutes`); - if (this.totalSeconds >= 1) parts.push(`${this.seconds} seconds`); - if (includeMillis) parts.push(`${this.millis} milliseconds`); - return parts.join(", "); - } + toString(includeWeeks = true, includeMillis = true) { + const parts = []; + if (this.totalYears >= 1) parts.push(`${this.totalYears} years`); + if (this.totalMonths >= 1) parts.push(`${this.months} months`); + if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks} weeks`); + if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days} days`); + if (this.totalHours >= 1) parts.push(`${this.hours} hours`); + if (this.totalMinutes >= 1) parts.push(`${this.minutes} minutes`); + if (this.totalSeconds >= 1) parts.push(`${this.seconds} seconds`); + if (includeMillis) parts.push(`${this.millis} milliseconds`); + return parts.join(", "); + } - toShortString(includeWeeks = true, includeMillis = true, withSpaces = false) { - const parts = []; - if (this.totalYears >= 1) parts.push(`${this.totalYears}y`); - if (this.totalMonths >= 1) parts.push(`${this.months}mo`); - if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks}w`); - if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days}d`); - if (this.totalHours >= 1) parts.push(`${this.hours}h`); - if (this.totalMinutes >= 1) parts.push(`${this.minutes}m`); - if (this.totalSeconds >= 1) parts.push(`${this.seconds}s`); - if (includeMillis) parts.push(`${this.millis}ms`); - return parts.join(withSpaces ? " " : ""); - } + toShortString(includeWeeks = true, includeMillis = true, withSpaces = false) { + const parts = []; + if (this.totalYears >= 1) parts.push(`${this.totalYears}y`); + if (this.totalMonths >= 1) parts.push(`${this.months}mo`); + if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks}w`); + if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days}d`); + if (this.totalHours >= 1) parts.push(`${this.hours}h`); + if (this.totalMinutes >= 1) parts.push(`${this.minutes}m`); + if (this.totalSeconds >= 1) parts.push(`${this.seconds}s`); + if (includeMillis) parts.push(`${this.millis}ms`); + return parts.join(withSpaces ? " " : ""); + } - get startDate() { - return new Date(this.start); - } + get startDate() { + return new Date(this.start); + } - get endDate() { - return new Date(this.end); - } + get endDate() { + return new Date(this.end); + } } diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts index e6dff76..56280e7 100644 --- a/src/util/util/Token.ts +++ b/src/util/util/Token.ts @@ -36,215 +36,215 @@ export const CurrentTokenFormatVersion: number = 3; export type UserTokenData = { - user: User; - session?: Session; - tokenVersion: number; - decoded: { - id: string; - iat: number; - ver?: number; // token format version - did?: string; // device id - }; + user: User; + session?: Session; + tokenVersion: number; + decoded: { + id: string; + iat: number; + ver?: number; // token format version + did?: string; // device id + }; }; function logAuth(text: string) { - if (process.env.LOG_AUTH !== "true") return; - console.log(`[AUTH] ${text}`); + if (process.env.LOG_AUTH !== "true") return; + console.log(`[AUTH] ${text}`); } function rejectAndLog(rejectFunction: (reason?: string) => void, reason: string) { - console.error(reason); - rejectFunction(reason); + console.error(reason); + rejectFunction(reason); } export const checkToken = ( - token: string, - opts?: { - select?: FindOptionsSelectByString; - relations?: FindOptionsRelationByString; - ipAddress?: string; - fingerprint?: string; - }, + token: string, + opts?: { + select?: FindOptionsSelectByString; + relations?: FindOptionsRelationByString; + ipAddress?: string; + fingerprint?: string; + }, ): Promise => { - return new Promise((resolve, reject) => { - token = token.replace("Bot ", ""); // there is no bot distinction in sb - token = token.replace("Bearer ", ""); // allow bearer tokens + return new Promise((resolve, reject) => { + token = token.replace("Bot ", ""); // there is no bot distinction in sb + token = token.replace("Bearer ", ""); // allow bearer tokens - let legacyVersion: number | undefined = undefined; + let legacyVersion: number | undefined = undefined; - const validateUser: jwt.VerifyCallback = async (err, out) => { - const decoded = out as UserTokenData["decoded"]; - if (err || !decoded) { - logAuth("validateUser rejected: " + err); - return rejectAndLog(reject, "Invalid Token meow " + err); - } + const validateUser: jwt.VerifyCallback = async (err, out) => { + const decoded = out as UserTokenData["decoded"]; + if (err || !decoded) { + logAuth("validateUser rejected: " + err); + return rejectAndLog(reject, "Invalid Token meow " + err); + } - const [user, session] = await Promise.all([ - User.findOne({ - where: { id: decoded.id }, - select: [...(opts?.select || []), "id", "bot", "disabled", "deleted", "rights", "data"], - relations: opts?.relations, - }), - decoded.did ? Session.findOne({ where: { session_id: decoded.did, user_id: decoded.id } }) : undefined, - ]); + const [user, session] = await Promise.all([ + User.findOne({ + where: { id: decoded.id }, + select: [...(opts?.select || []), "id", "bot", "disabled", "deleted", "rights", "data"], + relations: opts?.relations, + }), + decoded.did ? Session.findOne({ where: { session_id: decoded.did, user_id: decoded.id } }) : undefined, + ]); - if (!user) { - logAuth("validateUser rejected: User not found"); - return rejectAndLog(reject, "User not found"); - } + if (!user) { + logAuth("validateUser rejected: User not found"); + return rejectAndLog(reject, "User not found"); + } - if (decoded.did && !session) { - logAuth("validateUser rejected: Session not found"); - return rejectAndLog(reject, "Invalid Token"); - } + if (decoded.did && !session) { + logAuth("validateUser rejected: Session not found"); + return rejectAndLog(reject, "Invalid Token"); + } - // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds - if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) { - logAuth("validateUser rejected: Token not yet valid"); - return rejectAndLog(reject, "Invalid Token"); - } + // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds + if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) { + logAuth("validateUser rejected: Token not yet valid"); + return rejectAndLog(reject, "Invalid Token"); + } - if (user.disabled) { - logAuth("validateUser rejected: User disabled"); - return rejectAndLog(reject, "User disabled"); - } + if (user.disabled) { + logAuth("validateUser rejected: User disabled"); + return rejectAndLog(reject, "User disabled"); + } - if (user.deleted) { - logAuth("validateUser rejected: User deleted"); - return rejectAndLog(reject, "User not found"); - } + if (user.deleted) { + logAuth("validateUser rejected: User deleted"); + return rejectAndLog(reject, "User not found"); + } - const banReasons = await InstanceBan.findInstanceBans({ userId: user.id, ipAddress: opts?.ipAddress, fingerprint: opts?.fingerprint, propagateBan: true }); - if (banReasons.length > 0) { - logAuth("validateUser rejected: User banned for reasons: " + banReasons.join(", ")); - return rejectAndLog(reject, "Invalid Token"); - } + const banReasons = await InstanceBan.findInstanceBans({ userId: user.id, ipAddress: opts?.ipAddress, fingerprint: opts?.fingerprint, propagateBan: true }); + if (banReasons.length > 0) { + logAuth("validateUser rejected: User banned for reasons: " + banReasons.join(", ")); + return rejectAndLog(reject, "Invalid Token"); + } - if (session && TimeSpan.fromDates(session.last_seen?.getTime() ?? 0, new Date().getTime()).totalSeconds >= 15) { - session.last_seen = new Date(); - let updateIpInfoPromise; - if (opts?.ipAddress && opts?.ipAddress !== session.last_seen_ip) { - session.last_seen_ip = opts.ipAddress; - updateIpInfoPromise = session.updateIpInfo(); - } - await Promise.all([session.save(), updateIpInfoPromise]); - } + if (session && TimeSpan.fromDates(session.last_seen?.getTime() ?? 0, new Date().getTime()).totalSeconds >= 15) { + session.last_seen = new Date(); + let updateIpInfoPromise; + if (opts?.ipAddress && opts?.ipAddress !== session.last_seen_ip) { + session.last_seen_ip = opts.ipAddress; + updateIpInfoPromise = session.updateIpInfo(); + } + await Promise.all([session.save(), updateIpInfoPromise]); + } - const result: UserTokenData = { - decoded, - session: session ?? undefined, - user, - // v1 can be told apart, v2 cant outside of missing device id and version - tokenVersion: decoded.ver ?? legacyVersion ?? 2, - }; + const result: UserTokenData = { + decoded, + session: session ?? undefined, + user, + // v1 can be told apart, v2 cant outside of missing device id and version + tokenVersion: decoded.ver ?? legacyVersion ?? 2, + }; - if (process.env.LOG_TOKEN_VERSION) console.log("User", user.id, "logged in with token version", result.tokenVersion); + if (process.env.LOG_TOKEN_VERSION) console.log("User", user.id, "logged in with token version", result.tokenVersion); - logAuth("validateUser success: " + JSON.stringify(result)); - return resolve(result); - }; + logAuth("validateUser success: " + JSON.stringify(result)); + return resolve(result); + }; - const dec = jwt.decode(token, { complete: true }); - if (!dec) return reject("Could not parse token"); - logAuth("Decoded token: " + JSON.stringify(dec)); + const dec = jwt.decode(token, { complete: true }); + if (!dec) return reject("Could not parse token"); + logAuth("Decoded token: " + JSON.stringify(dec)); - if (dec.header.alg == "HS256" && Config.get().security.jwtSecret !== null) { - legacyVersion = 1; - jwt.verify(token, Config.get().security.jwtSecret!, { algorithms: ["HS256"] }, validateUser); - } else if (dec.header.alg == "ES512") { - loadOrGenerateKeypair().then((keyPair) => { - jwt.verify(token, keyPair.publicKey, { algorithms: ["ES512"] }, validateUser); - }); - } else return reject("Invalid token algorithm"); - }); + if (dec.header.alg == "HS256" && Config.get().security.jwtSecret !== null) { + legacyVersion = 1; + jwt.verify(token, Config.get().security.jwtSecret!, { algorithms: ["HS256"] }, validateUser); + } else if (dec.header.alg == "ES512") { + loadOrGenerateKeypair().then((keyPair) => { + jwt.verify(token, keyPair.publicKey, { algorithms: ["ES512"] }, validateUser); + }); + } else return reject("Invalid token algorithm"); + }); }; export async function generateToken(id: string, isAdminSession: boolean = false): Promise { - const iat = Math.floor(Date.now() / 1000); - const keyPair = await loadOrGenerateKeypair(); + const iat = Math.floor(Date.now() / 1000); + const keyPair = await loadOrGenerateKeypair(); - let newSession; - do { - newSession = Session.create({ - session_id: randomUpperString(10), // readable at a glance - user_id: id, - is_admin_session: isAdminSession, - client_status: {}, - status: "online", - client_info: {}, - }); - } while (await Session.findOne({ where: { session_id: newSession.session_id } })); + let newSession; + do { + newSession = Session.create({ + session_id: randomUpperString(10), // readable at a glance + user_id: id, + is_admin_session: isAdminSession, + client_status: {}, + status: "online", + client_info: {}, + }); + } while (await Session.findOne({ where: { session_id: newSession.session_id } })); - await newSession.save(); + await newSession.save(); - return new Promise((res, rej) => { - const payload = { id, iat, kid: keyPair.fingerprint, ver: CurrentTokenFormatVersion, did: newSession.session_id } as UserTokenData["decoded"]; - jwt.sign( - payload, - keyPair.privateKey, - { - algorithm: "ES512", - }, - (err, token) => { - if (err) return rej(err); - return res(token); - }, - ); - }); + return new Promise((res, rej) => { + const payload = { id, iat, kid: keyPair.fingerprint, ver: CurrentTokenFormatVersion, did: newSession.session_id } as UserTokenData["decoded"]; + jwt.sign( + payload, + keyPair.privateKey, + { + algorithm: "ES512", + }, + (err, token) => { + if (err) return rej(err); + return res(token); + }, + ); + }); } let lastFsCheck: number; let cachedKeypair: { - privateKey: crypto.KeyObject; - publicKey: crypto.KeyObject; - fingerprint: string; + privateKey: crypto.KeyObject; + publicKey: crypto.KeyObject; + fingerprint: string; }; // Get ECDSA keypair from file or generate it export async function loadOrGenerateKeypair() { - if (cachedKeypair) { - // check for file deletion every minute - if (Date.now() - lastFsCheck > 60000) { - if (!existsSync("jwt.key") || !existsSync("jwt.key.pub")) { - console.log("[JWT] Keypair files disappeared... Saving them again."); - await Promise.all([ - fs.writeFile("jwt.key", cachedKeypair.privateKey.export({ format: "pem", type: "sec1" })), - fs.writeFile("jwt.key.pub", cachedKeypair.publicKey.export({ format: "pem", type: "spki" })), - ]); - } - lastFsCheck = Date.now(); - } + if (cachedKeypair) { + // check for file deletion every minute + if (Date.now() - lastFsCheck > 60000) { + if (!existsSync("jwt.key") || !existsSync("jwt.key.pub")) { + console.log("[JWT] Keypair files disappeared... Saving them again."); + await Promise.all([ + fs.writeFile("jwt.key", cachedKeypair.privateKey.export({ format: "pem", type: "sec1" })), + fs.writeFile("jwt.key.pub", cachedKeypair.publicKey.export({ format: "pem", type: "spki" })), + ]); + } + lastFsCheck = Date.now(); + } - return cachedKeypair; - } + return cachedKeypair; + } - let privateKey: crypto.KeyObject; - let publicKey: crypto.KeyObject; + let privateKey: crypto.KeyObject; + let publicKey: crypto.KeyObject; - if (existsSync("jwt.key") && existsSync("jwt.key.pub")) { - const [loadedPrivateKey, loadedPublicKey] = await Promise.all([fs.readFile("jwt.key"), fs.readFile("jwt.key.pub")]); + if (existsSync("jwt.key") && existsSync("jwt.key.pub")) { + const [loadedPrivateKey, loadedPublicKey] = await Promise.all([fs.readFile("jwt.key"), fs.readFile("jwt.key.pub")]); - privateKey = crypto.createPrivateKey(loadedPrivateKey); - publicKey = crypto.createPublicKey(loadedPublicKey); - } else { - console.log("[JWT] Generating new keypair"); - const res = crypto.generateKeyPairSync("ec", { - namedCurve: "secp521r1", - }); - privateKey = res.privateKey; - publicKey = res.publicKey; + privateKey = crypto.createPrivateKey(loadedPrivateKey); + publicKey = crypto.createPublicKey(loadedPublicKey); + } else { + console.log("[JWT] Generating new keypair"); + const res = crypto.generateKeyPairSync("ec", { + namedCurve: "secp521r1", + }); + privateKey = res.privateKey; + publicKey = res.publicKey; - await Promise.all([ - fs.writeFile("jwt.key", privateKey.export({ format: "pem", type: "sec1" })), - fs.writeFile("jwt.key.pub", publicKey.export({ format: "pem", type: "spki" })), - ]); - } + await Promise.all([ + fs.writeFile("jwt.key", privateKey.export({ format: "pem", type: "sec1" })), + fs.writeFile("jwt.key.pub", publicKey.export({ format: "pem", type: "spki" })), + ]); + } - const fingerprint = crypto - .createHash("sha256") - .update(publicKey.export({ format: "pem", type: "spki" })) - .digest("hex"); + const fingerprint = crypto + .createHash("sha256") + .update(publicKey.export({ format: "pem", type: "spki" })) + .digest("hex"); - lastFsCheck = Date.now(); - return (cachedKeypair = { privateKey, publicKey, fingerprint }); + lastFsCheck = Date.now(); + return (cachedKeypair = { privateKey, publicKey, fingerprint }); } diff --git a/src/util/util/TraverseDirectory.ts b/src/util/util/TraverseDirectory.ts index f2e222c..09923aa 100644 --- a/src/util/util/TraverseDirectory.ts +++ b/src/util/util/TraverseDirectory.ts @@ -24,5 +24,5 @@ const DEFAULT_FILTER = new RegExp("^([^.].*)(? a.localeCompare(b)); - u.search = params.length ? "?" + params.map(([k, v]) => `${k}=${v}`).join("&") : ""; - } else { - // Ensure no empty search string - u.search = ""; - } - return u.toString(); - } catch (e) { - return input; - } + try { + const u = new URL(input); + // Remove fragment + u.hash = ""; + // Normalize pathname - remove trailing slash except for root "/" + if (u.pathname !== "/" && u.pathname.endsWith("/")) { + u.pathname = u.pathname.slice(0, -1); + } + // Normalize query params: sort by key + if (u.search) { + const params = Array.from(u.searchParams.entries()); + params.sort(([a], [b]) => a.localeCompare(b)); + u.search = params.length ? "?" + params.map(([k, v]) => `${k}=${v}`).join("&") : ""; + } else { + // Ensure no empty search string + u.search = ""; + } + return u.toString(); + } catch (e) { + return input; + } } diff --git a/src/util/util/WebAuthn.ts b/src/util/util/WebAuthn.ts index bab0ad2..b69af5c 100644 --- a/src/util/util/WebAuthn.ts +++ b/src/util/util/WebAuthn.ts @@ -22,43 +22,43 @@ import { loadOrGenerateKeypair } from "./Token"; const jwtSignOptions: jwt.SignOptions = { - algorithm: "ES512", - expiresIn: "5m", + algorithm: "ES512", + expiresIn: "5m", }; const jwtVerifyOptions: jwt.VerifyOptions = { - algorithms: ["ES512"], + algorithms: ["ES512"], }; export const WebAuthn: { - fido2: Fido2Lib | null; - init: () => void; + fido2: Fido2Lib | null; + init: () => void; } = { - fido2: null, - init: function () { - this.fido2 = new Fido2Lib({ - challengeSize: 128, - }); - }, + fido2: null, + init: function () { + this.fido2 = new Fido2Lib({ + challengeSize: 128, + }); + }, }; export async function generateWebAuthnTicket(challenge: string): Promise { - return new Promise((res, rej) => { - loadOrGenerateKeypair().then((kp) => - jwt.sign({ challenge }, kp.privateKey, jwtSignOptions, (err, token) => { - if (err || !token) return rej(err || "no token"); - return res(token); - }), - ); - }); + return new Promise((res, rej) => { + loadOrGenerateKeypair().then((kp) => + jwt.sign({ challenge }, kp.privateKey, jwtSignOptions, (err, token) => { + if (err || !token) return rej(err || "no token"); + return res(token); + }), + ); + }); } export async function verifyWebAuthnToken(token: string) { - return new Promise((res, rej) => { - loadOrGenerateKeypair().then((kp) => - jwt.verify(token, kp.publicKey, jwtVerifyOptions, async (err, decoded) => { - if (err) return rej(err); - return res(decoded); - }), - ); - }); + return new Promise((res, rej) => { + loadOrGenerateKeypair().then((kp) => + jwt.verify(token, kp.publicKey, jwtVerifyOptions, async (err, decoded) => { + if (err) return rej(err); + return res(decoded); + }), + ); + }); } diff --git a/src/util/util/cdn.ts b/src/util/util/cdn.ts index 115fcb4..91073a5 100644 --- a/src/util/util/cdn.ts +++ b/src/util/util/cdn.ts @@ -22,59 +22,59 @@ import { Config } from "./Config"; export async function uploadFile( - path: string, - // These are the only props we use, don't need to enforce the full type. - file?: Pick, + path: string, + // These are the only props we use, don't need to enforce the full type. + file?: Pick, ): Promise { - if (!file?.buffer) throw new HTTPError("Missing file in body"); + if (!file?.buffer) throw new HTTPError("Missing file in body"); - const form = new FormData(); - form.append("file", file.buffer, { - contentType: file.mimetype, - filename: file.originalname, - }); + const form = new FormData(); + form.append("file", file.buffer, { + contentType: file.mimetype, + filename: file.originalname, + }); - const response = await fetch(`${Config.get().cdn.endpointPrivate}${path}`, { - headers: { - signature: Config.get().security.requestSignature, - ...form.getHeaders(), - }, - method: "POST", - body: form.getBuffer(), - }); - const result = (await response.json()) as Attachment; + const response = await fetch(`${Config.get().cdn.endpointPrivate}${path}`, { + headers: { + signature: Config.get().security.requestSignature, + ...form.getHeaders(), + }, + method: "POST", + body: form.getBuffer(), + }); + const result = (await response.json()) as Attachment; - if (response.status !== 200) throw result; - return result; + if (response.status !== 200) throw result; + return result; } export async function handleFile(path: string, body?: string): Promise { - if (!body || !body.startsWith("data:")) return undefined; - try { - const mimetype = body.split(":")[1].split(";")[0]; - const buffer = Buffer.from(body.split(",")[1], "base64"); + if (!body || !body.startsWith("data:")) return undefined; + try { + const mimetype = body.split(":")[1].split(";")[0]; + const buffer = Buffer.from(body.split(",")[1], "base64"); - const { id } = await uploadFile(path, { - buffer, - mimetype, - originalname: "banner", - }); - return id; - } catch (error) { - console.error(error); - throw new HTTPError("Invalid " + path); - } + const { id } = await uploadFile(path, { + buffer, + mimetype, + originalname: "banner", + }); + return id; + } catch (error) { + console.error(error); + throw new HTTPError("Invalid " + path); + } } export async function deleteFile(path: string) { - const response = await fetch(`${Config.get().cdn.endpointPrivate}${path}`, { - headers: { - signature: Config.get().security.requestSignature, - }, - method: "DELETE", - }); - const result = await response.json(); + const response = await fetch(`${Config.get().cdn.endpointPrivate}${path}`, { + headers: { + signature: Config.get().security.requestSignature, + }, + method: "DELETE", + }); + const result = await response.json(); - if (response.status !== 200) throw result; - return result; + if (response.status !== 200) throw result; + return result; } diff --git a/src/util/util/email/clients/IEmailClient.ts b/src/util/util/email/clients/IEmailClient.ts index 261bffa..9f118a9 100644 --- a/src/util/util/email/clients/IEmailClient.ts +++ b/src/util/util/email/clients/IEmailClient.ts @@ -17,22 +17,22 @@ */ export interface IEmail { - from: string; - to: string; - subject: string; - text: string; - html: string; + from: string; + to: string; + subject: string; + text: string; + html: string; } export interface IEmailClient { - init: () => Promise; - sendMail: (email: IEmail) => Promise; + init: () => Promise; + sendMail: (email: IEmail) => Promise; } export class BaseEmailClient implements IEmailClient { - async init(): Promise { - return; - } - sendMail(email: IEmail): Promise { - throw new Error("Method not implemented."); - } + async init(): Promise { + return; + } + sendMail(email: IEmail): Promise { + throw new Error("Method not implemented."); + } } diff --git a/src/util/util/email/clients/MailGunEmailClient.ts b/src/util/util/email/clients/MailGunEmailClient.ts index c5dea79..d0a1613 100644 --- a/src/util/util/email/clients/MailGunEmailClient.ts +++ b/src/util/util/email/clients/MailGunEmailClient.ts @@ -21,45 +21,45 @@ // NOTE: mailgun supports SMTP, is there any point in maintaining this? export class MailGunEmailClient extends BaseEmailClient { - mailGun?: unknown; - mailGunClient?: unknown; - // mailGun?: import("mailgun.js").default; // for development - doesn't work if package isn't installed - // mailGunClient?: import("mailgun.js/Classes/MailgunClient").default; // for development - doesn't work if package isn't installed - override async init(): Promise { - // get configuration - const { apiKey, username, domain, isEuropean } = Config.get().email.mailgun; + mailGun?: unknown; + mailGunClient?: unknown; + // mailGun?: import("mailgun.js").default; // for development - doesn't work if package isn't installed + // mailGunClient?: import("mailgun.js/Classes/MailgunClient").default; // for development - doesn't work if package isn't installed + override async init(): Promise { + // get configuration + const { apiKey, username, domain, isEuropean } = Config.get().email.mailgun; - // ensure all required configuration values are set - if (!apiKey || !domain || !username) return console.error("[Email] Mailgun has not been configured correctly."); + // ensure all required configuration values are set + if (!apiKey || !domain || !username) return console.error("[Email] Mailgun has not been configured correctly."); - try { - // try to import the transporter package - this.mailGun = new (await import("mailgun.js")).default(FormData); - } catch { - // if the package is not installed, log an error and return void so we don't set the transporter - console.error("[Email] MailGun transport is not installed. Please run `npm install mailgun.js --save-optional` to install it."); - return; - } + try { + // try to import the transporter package + this.mailGun = new (await import("mailgun.js")).default(FormData); + } catch { + // if the package is not installed, log an error and return void so we don't set the transporter + console.error("[Email] MailGun transport is not installed. Please run `npm install mailgun.js --save-optional` to install it."); + return; + } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - this.mailGun.client({ username: username, key: apiKey, url: isEuropean ? "https://api.eu.mailgun.net" : undefined }); - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + this.mailGun.client({ username: username, key: apiKey, url: isEuropean ? "https://api.eu.mailgun.net" : undefined }); + } - override async sendMail(email: IEmail): Promise { - if (!this.mailGun) throw new Error("MailGun not initialized"); - if (!this.mailGunClient) throw new Error("MailGun not initialized"); - const { domain } = Config.get().email.mailgun; - if (!domain) throw new Error("MailGun domain not configured"); + override async sendMail(email: IEmail): Promise { + if (!this.mailGun) throw new Error("MailGun not initialized"); + if (!this.mailGunClient) throw new Error("MailGun not initialized"); + const { domain } = Config.get().email.mailgun; + if (!domain) throw new Error("MailGun domain not configured"); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - await this.mailGunClient.messages.create(domain, { - to: email.to, - from: email.from, - subject: email.subject, - text: email.text, - html: email.html, - }); - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + await this.mailGunClient.messages.create(domain, { + to: email.to, + from: email.from, + subject: email.subject, + text: email.text, + html: email.html, + }); + } } diff --git a/src/util/util/email/clients/MailJetEmailClient.ts b/src/util/util/email/clients/MailJetEmailClient.ts index 1d771df..45664eb 100644 --- a/src/util/util/email/clients/MailJetEmailClient.ts +++ b/src/util/util/email/clients/MailJetEmailClient.ts @@ -20,52 +20,52 @@ import { Config } from "@spacebar/util"; export class MailJetEmailClient extends BaseEmailClient { - mailJet?: unknown; - // mailJet?: import("node-mailjet").default; // for development - doesn't work if package isn't installed - override async init(): Promise { - // get configuration - const { apiKey, apiSecret } = Config.get().email.mailjet; + mailJet?: unknown; + // mailJet?: import("node-mailjet").default; // for development - doesn't work if package isn't installed + override async init(): Promise { + // get configuration + const { apiKey, apiSecret } = Config.get().email.mailjet; - // ensure all required configuration values are set - if (!apiKey || !apiSecret) return console.error("[Email] Mailjet has not been configured correctly."); + // ensure all required configuration values are set + if (!apiKey || !apiSecret) return console.error("[Email] Mailjet has not been configured correctly."); - try { - // try to import the transporter package - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - this.mailJet = new (await import("node-mailjet")).default({ - apiKey: apiKey, - apiSecret: apiSecret, - }); - } catch { - // if the package is not installed, log an error and return void so we don't set the transporter - console.error("[Email] MailJet transport is not installed. Please run `npm install node-mailjet --save-optional` to install it."); - return; - } - } + try { + // try to import the transporter package + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + this.mailJet = new (await import("node-mailjet")).default({ + apiKey: apiKey, + apiSecret: apiSecret, + }); + } catch { + // if the package is not installed, log an error and return void so we don't set the transporter + console.error("[Email] MailJet transport is not installed. Please run `npm install node-mailjet --save-optional` to install it."); + return; + } + } - override async sendMail(email: IEmail): Promise { - if (!this.mailJet) throw new Error("mailJet not initialized"); + override async sendMail(email: IEmail): Promise { + if (!this.mailJet) throw new Error("mailJet not initialized"); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - await this.mailJet.post("send", { version: "v3.1" }).request({ - Messages: [ - { - From: { - Email: Config.get().email.senderAddress, - Name: Config.get().general.instanceName, - }, - To: [ - { - Email: email.to, - }, - ], - Subject: email.subject, - TextPart: email.text, - HTMLPart: email.html, - }, - ], - }); - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + await this.mailJet.post("send", { version: "v3.1" }).request({ + Messages: [ + { + From: { + Email: Config.get().email.senderAddress, + Name: Config.get().general.instanceName, + }, + To: [ + { + Email: email.to, + }, + ], + Subject: email.subject, + TextPart: email.text, + HTMLPart: email.html, + }, + ], + }); + } } diff --git a/src/util/util/email/clients/SMTPEmailClient.ts b/src/util/util/email/clients/SMTPEmailClient.ts index b1b6ad9..46bfe0f 100644 --- a/src/util/util/email/clients/SMTPEmailClient.ts +++ b/src/util/util/email/clients/SMTPEmailClient.ts @@ -20,62 +20,62 @@ import { Config } from "@spacebar/util"; export class SMTPEmailClient extends BaseEmailClient { - // sendGrid?: unknown; - nodemailer?: typeof import("nodemailer"); - transporter: import("nodemailer").Transporter; - override async init(): Promise { - try { - // try to import the transporter package - this.nodemailer = (await import("nodemailer")).default; - } catch { - // if the package is not installed, log an error and return void so we don't set the transporter - console.error("[Email] nodemailer is not installed. Please run `npm install nodemailer --save-optional` to install it."); - return; - } - // get configuration - const { host, port, secure, username, password } = Config.get().email.smtp; + // sendGrid?: unknown; + nodemailer?: typeof import("nodemailer"); + transporter: import("nodemailer").Transporter; + override async init(): Promise { + try { + // try to import the transporter package + this.nodemailer = (await import("nodemailer")).default; + } catch { + // if the package is not installed, log an error and return void so we don't set the transporter + console.error("[Email] nodemailer is not installed. Please run `npm install nodemailer --save-optional` to install it."); + return; + } + // get configuration + const { host, port, secure, username, password } = Config.get().email.smtp; - // ensure all required configuration values are set - if (!host || !port || secure === null || !username || !password) return console.error("[Email] SMTP has not been configured correctly."); + // ensure all required configuration values are set + if (!host || !port || secure === null || !username || !password) return console.error("[Email] SMTP has not been configured correctly."); - if (!Config.get().email.senderAddress && !Config.get().general.correspondenceEmail) - return console.error( - '[Email] You have to configure either "email_senderAddress" or "general_correspondenceEmail" for emails to work. The configured value is used as the sender address.', - ); + if (!Config.get().email.senderAddress && !Config.get().general.correspondenceEmail) + return console.error( + '[Email] You have to configure either "email_senderAddress" or "general_correspondenceEmail" for emails to work. The configured value is used as the sender address.', + ); - // construct the transporter - const transporter = this.nodemailer.createTransport({ - host, - port, - secure, - auth: { - user: username, - pass: password, - }, - }); + // construct the transporter + const transporter = this.nodemailer.createTransport({ + host, + port, + secure, + auth: { + user: username, + pass: password, + }, + }); - // verify connection configuration - const verified = await transporter.verify().catch((err) => { - console.error("[Email] SMTP verification failed:", err); - return; - }); + // verify connection configuration + const verified = await transporter.verify().catch((err) => { + console.error("[Email] SMTP verification failed:", err); + return; + }); - // if verification failed, return void and don't set transporter - if (!verified) return; + // if verification failed, return void and don't set transporter + if (!verified) return; - this.transporter = transporter; - } + this.transporter = transporter; + } - override async sendMail(email: IEmail): Promise { - if (!this.nodemailer) throw new Error("nodemailer not initialized"); - if (!this.transporter) throw new Error("nodemailer transporter not initialized"); + override async sendMail(email: IEmail): Promise { + if (!this.nodemailer) throw new Error("nodemailer not initialized"); + if (!this.transporter) throw new Error("nodemailer transporter not initialized"); - await this.transporter.sendMail({ - to: email.to, - from: email.from, - subject: email.subject, - text: email.text, - html: email.html, - }); - } + await this.transporter.sendMail({ + to: email.to, + from: email.from, + subject: email.subject, + text: email.text, + html: email.html, + }); + } } diff --git a/src/util/util/email/clients/SendGridEmailClient.ts b/src/util/util/email/clients/SendGridEmailClient.ts index 236a61e..8eb3fae 100644 --- a/src/util/util/email/clients/SendGridEmailClient.ts +++ b/src/util/util/email/clients/SendGridEmailClient.ts @@ -20,40 +20,40 @@ import { Config } from "@spacebar/util"; export class SendGridEmailClient extends BaseEmailClient { - sendGrid?: unknown; - // sendGrid?: typeof import("@sendgrid/mail"); // for development - doesn't work if package isn't installed - override async init(): Promise { - // get configuration - const { apiKey } = Config.get().email.sendgrid; + sendGrid?: unknown; + // sendGrid?: typeof import("@sendgrid/mail"); // for development - doesn't work if package isn't installed + override async init(): Promise { + // get configuration + const { apiKey } = Config.get().email.sendgrid; - // ensure all required configuration values are set - if (!apiKey) return console.error("[Email] SendGrid has not been configured correctly."); + // ensure all required configuration values are set + if (!apiKey) return console.error("[Email] SendGrid has not been configured correctly."); - try { - // try to import the transporter package - this.sendGrid = (await import("@sendgrid/mail")).default; - } catch { - // if the package is not installed, log an error and return void so we don't set the transporter - console.error("[Email] SendGrid transport is not installed. Please run `npm install @sendgrid/mail --save-optional` to install it."); - return; - } + try { + // try to import the transporter package + this.sendGrid = (await import("@sendgrid/mail")).default; + } catch { + // if the package is not installed, log an error and return void so we don't set the transporter + console.error("[Email] SendGrid transport is not installed. Please run `npm install @sendgrid/mail --save-optional` to install it."); + return; + } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - this.sendGrid.setApiKey(apiKey); - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + this.sendGrid.setApiKey(apiKey); + } - override async sendMail(email: IEmail): Promise { - if (!this.sendGrid) throw new Error("SendGrid not initialized"); + override async sendMail(email: IEmail): Promise { + if (!this.sendGrid) throw new Error("SendGrid not initialized"); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - await this.sendGrid.send({ - to: email.to, - from: email.from, - subject: email.subject, - text: email.text, - html: email.html, - }); - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + await this.sendGrid.send({ + to: email.to, + from: email.from, + subject: email.subject, + text: email.text, + html: email.html, + }); + } } diff --git a/src/util/util/email/index.ts b/src/util/util/email/index.ts index 729d5dd..ca87e27 100644 --- a/src/util/util/email/index.ts +++ b/src/util/util/email/index.ts @@ -30,185 +30,185 @@ const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "..", "assets"); export enum MailTypes { - verifyEmail = "verifyEmail", - resetPassword = "resetPassword", - changePassword = "changePassword", + verifyEmail = "verifyEmail", + resetPassword = "resetPassword", + changePassword = "changePassword", } export const Email: { - transporter: IEmailClient | null; - init: () => Promise; - generateLink: (type: Omit, id: string) => Promise; - sendMail: (type: MailTypes, user: User, email: string) => Promise; - sendVerifyEmail: (user: User, email: string) => Promise; - sendResetPassword: (user: User, email: string) => Promise; - sendPasswordChanged: (user: User, email: string) => Promise; - doReplacements: ( - template: string, - user: User, - actionUrl?: string, - ipInfo?: { - ip: string; - city: string; - region: string; - country_name: string; - }, - ) => string; + transporter: IEmailClient | null; + init: () => Promise; + generateLink: (type: Omit, id: string) => Promise; + sendMail: (type: MailTypes, user: User, email: string) => Promise; + sendVerifyEmail: (user: User, email: string) => Promise; + sendResetPassword: (user: User, email: string) => Promise; + sendPasswordChanged: (user: User, email: string) => Promise; + doReplacements: ( + template: string, + user: User, + actionUrl?: string, + ipInfo?: { + ip: string; + city: string; + region: string; + country_name: string; + }, + ) => string; } = { - transporter: null, - init: async function () { - const { provider } = Config.get().email; - if (!provider) return; + transporter: null, + init: async function () { + const { provider } = Config.get().email; + if (!provider) return; - switch (provider) { - case "smtp": - this.transporter = new SMTPEmailClient(); - break; - case "sendgrid": - this.transporter = new SendGridEmailClient(); - break; - case "mailgun": - this.transporter = new MailGunEmailClient(); - break; - case "mailjet": - this.transporter = new MailJetEmailClient(); - break; - default: - console.error(`[Email] Invalid provider: ${provider}`); - return; - } + switch (provider) { + case "smtp": + this.transporter = new SMTPEmailClient(); + break; + case "sendgrid": + this.transporter = new SendGridEmailClient(); + break; + case "mailgun": + this.transporter = new MailGunEmailClient(); + break; + case "mailjet": + this.transporter = new MailJetEmailClient(); + break; + default: + console.error(`[Email] Invalid provider: ${provider}`); + return; + } - if (!this.transporter) return console.error(`[Email] Invalid provider: ${provider}`); - console.log(`[Email] Initializing ${provider} transport...`); - await this.transporter.init(); - console.log(`[Email] ${provider} transport initialized.`); - }, + if (!this.transporter) return console.error(`[Email] Invalid provider: ${provider}`); + console.log(`[Email] Initializing ${provider} transport...`); + await this.transporter.init(); + console.log(`[Email] ${provider} transport initialized.`); + }, - /** - * Replaces all placeholders in an email template with the correct values - */ - doReplacements: function ( - template, - user, - actionUrl?, - ipInfo?: { - ip: string; - city: string; - region: string; - country_name: string; - }, - ) { - const { instanceName } = Config.get().general; + /** + * Replaces all placeholders in an email template with the correct values + */ + doReplacements: function ( + template, + user, + actionUrl?, + ipInfo?: { + ip: string; + city: string; + region: string; + country_name: string; + }, + ) { + const { instanceName } = Config.get().general; - const replacements = [ - ["{instanceName}", instanceName], - ["{userUsername}", user.username], - ["{userDiscriminator}", user.discriminator], - ["{userId}", user.id], - ["{phoneNumber}", user.phone?.slice(-4)], - ["{userEmail}", user.email], - ["{actionUrl}", actionUrl], - ["{ipAddress}", ipInfo?.ip], - ["{locationCity}", ipInfo?.city], - ["{locationRegion}", ipInfo?.region], - ["{locationCountryName}", ipInfo?.country_name], - ]; + const replacements = [ + ["{instanceName}", instanceName], + ["{userUsername}", user.username], + ["{userDiscriminator}", user.discriminator], + ["{userId}", user.id], + ["{phoneNumber}", user.phone?.slice(-4)], + ["{userEmail}", user.email], + ["{actionUrl}", actionUrl], + ["{ipAddress}", ipInfo?.ip], + ["{locationCity}", ipInfo?.city], + ["{locationRegion}", ipInfo?.region], + ["{locationCountryName}", ipInfo?.country_name], + ]; - // loop through all replacements and replace them in the template - for (const [key, value] of Object.values(replacements)) { - if (!value) continue; - template = template.replaceAll(key as string, value); - } + // loop through all replacements and replace them in the template + for (const [key, value] of Object.values(replacements)) { + if (!value) continue; + template = template.replaceAll(key as string, value); + } - return template; - }, + return template; + }, - /** - * - * @param id user id - */ - generateLink: async function (type, id) { - const token = (await generateToken(id)) as string; - // puyodead1: this is set to api endpoint because the verification page is on the server since no clients have one, and not all 3rd party clients will have one - const instanceUrl = Config.get().api.endpointPublic?.replace("/api", ""); - const dashedType = type.replace(/([A-Z])/g, "-$1").toLowerCase(); - const link = `${instanceUrl}/${dashedType}#token=${token}`; - return link; - }, + /** + * + * @param id user id + */ + generateLink: async function (type, id) { + const token = (await generateToken(id)) as string; + // puyodead1: this is set to api endpoint because the verification page is on the server since no clients have one, and not all 3rd party clients will have one + const instanceUrl = Config.get().api.endpointPublic?.replace("/api", ""); + const dashedType = type.replace(/([A-Z])/g, "-$1").toLowerCase(); + const link = `${instanceUrl}/${dashedType}#token=${token}`; + return link; + }, - /** - * - * @param type the MailType to send - * @param user the user to address it to - * @param email the email to send it to - * @returns - */ - sendMail: async function (type, user, email) { - if (!this.transporter) return; + /** + * + * @param type the MailType to send + * @param user the user to address it to + * @param email the email to send it to + * @returns + */ + sendMail: async function (type, user, email) { + if (!this.transporter) return; - const htmlTemplateNames: { [key in MailTypes]: string } = { - verifyEmail: "verify_email.html", - resetPassword: "password_reset_request.html", - changePassword: "password_changed.html", - }; + const htmlTemplateNames: { [key in MailTypes]: string } = { + verifyEmail: "verify_email.html", + resetPassword: "password_reset_request.html", + changePassword: "password_changed.html", + }; - const textTemplateNames: { [key in MailTypes]: string } = { - verifyEmail: "verify_email.txt", - resetPassword: "password_reset_request.txt", - changePassword: "password_changed.txt", - }; + const textTemplateNames: { [key in MailTypes]: string } = { + verifyEmail: "verify_email.txt", + resetPassword: "password_reset_request.txt", + changePassword: "password_changed.txt", + }; - const htmlTemplate = await fs.readFile(path.join(ASSET_FOLDER_PATH, "email_templates", htmlTemplateNames[type]), { encoding: "utf-8" }); + const htmlTemplate = await fs.readFile(path.join(ASSET_FOLDER_PATH, "email_templates", htmlTemplateNames[type]), { encoding: "utf-8" }); - const textTemplate = await fs.readFile(path.join(ASSET_FOLDER_PATH, "email_templates", textTemplateNames[type]), { encoding: "utf-8" }); + const textTemplate = await fs.readFile(path.join(ASSET_FOLDER_PATH, "email_templates", textTemplateNames[type]), { encoding: "utf-8" }); - // replace email template placeholders - const html = this.doReplacements( - htmlTemplate, - user, - // password change emails don't have links - type != MailTypes.changePassword ? await this.generateLink(type, user.id) : undefined, - ); + // replace email template placeholders + const html = this.doReplacements( + htmlTemplate, + user, + // password change emails don't have links + type != MailTypes.changePassword ? await this.generateLink(type, user.id) : undefined, + ); - const text = this.doReplacements( - textTemplate, - user, - // password change emails don't have links - type != MailTypes.changePassword ? await this.generateLink(type, user.id) : undefined, - ); + const text = this.doReplacements( + textTemplate, + user, + // password change emails don't have links + type != MailTypes.changePassword ? await this.generateLink(type, user.id) : undefined, + ); - // extract the title from the email template to use as the email subject - const subject = html.match(/(.*)<\/title>/)?.[1] || ""; + // extract the title from the email template to use as the email subject + const subject = html.match(/<title>(.*)<\/title>/)?.[1] || ""; - const message: IEmail = { - from: Config.get().email.senderAddress || Config.get().general.correspondenceEmail || "noreply@localhost", - to: email, - subject, - text, - html, - }; + const message: IEmail = { + from: Config.get().email.senderAddress || Config.get().general.correspondenceEmail || "noreply@localhost", + to: email, + subject, + text, + html, + }; - return this.transporter.sendMail(message); - }, + return this.transporter.sendMail(message); + }, - /** - * Sends an email to the user with a link to verify their email address - */ - sendVerifyEmail: async function (user, email) { - return this.sendMail(MailTypes.verifyEmail, user, email); - }, + /** + * Sends an email to the user with a link to verify their email address + */ + sendVerifyEmail: async function (user, email) { + return this.sendMail(MailTypes.verifyEmail, user, email); + }, - /** - * Sends an email to the user with a link to reset their password - */ - sendResetPassword: async function (user, email) { - return this.sendMail(MailTypes.resetPassword, user, email); - }, + /** + * Sends an email to the user with a link to reset their password + */ + sendResetPassword: async function (user, email) { + return this.sendMail(MailTypes.resetPassword, user, email); + }, - /** - * Sends an email to the user notifying them that their password has been changed - */ - sendPasswordChanged: async function (user, email) { - return this.sendMail(MailTypes.changePassword, user, email); - }, + /** + * Sends an email to the user notifying them that their password has been changed + */ + sendPasswordChanged: async function (user, email) { + return this.sendMail(MailTypes.changePassword, user, email); + }, }; diff --git a/src/util/util/extensions/Array.test.ts b/src/util/util/extensions/Array.test.ts index 8952c90..6984dfd 100644 --- a/src/util/util/extensions/Array.test.ts +++ b/src/util/util/extensions/Array.test.ts @@ -5,5 +5,5 @@ import assert from "node:assert/strict"; describe("Array extensions", () => { - // + // }); diff --git a/src/util/util/extensions/Array.ts b/src/util/util/extensions/Array.ts index bbd8616..f17e865 100644 --- a/src/util/util/extensions/Array.ts +++ b/src/util/util/extensions/Array.ts @@ -17,26 +17,26 @@ */ declare global { - interface Array<T> { - /** - * @deprecated never use, idk why but I can't get rid of this without errors - */ - remove(h: T): never; - } + interface Array<T> { + /** + * @deprecated never use, idk why but I can't get rid of this without errors + */ + remove(h: T): never; + } } /* https://stackoverflow.com/a/50636286 */ export function arrayPartition<T>(array: T[], filter: (elem: T) => boolean): [T[], T[]] { - const pass: T[] = [], - fail: T[] = []; - array.forEach((e) => (filter(e) ? pass : fail).push(e)); - return [pass, fail]; + const pass: T[] = [], + fail: T[] = []; + array.forEach((e) => (filter(e) ? pass : fail).push(e)); + return [pass, fail]; } export function arrayRemove<T>(array: T[], item: T): void { - const index = array.indexOf(item); - if (index > -1) { - array.splice(index, 1); - } + const index = array.indexOf(item); + if (index > -1) { + array.splice(index, 1); + } } // register extensions diff --git a/src/util/util/lambert-server/HTTPError.ts b/src/util/util/lambert-server/HTTPError.ts index 2302630..713098b 100644 --- a/src/util/util/lambert-server/HTTPError.ts +++ b/src/util/util/lambert-server/HTTPError.ts @@ -1,8 +1,8 @@ export class HTTPError extends Error { - constructor( - message: string, - public code: number = 400, - ) { - super(message); - } + constructor( + message: string, + public code: number = 400, + ) { + super(message); + } } diff --git a/src/util/util/lambert-server/Server.ts b/src/util/util/lambert-server/Server.ts index c848ba7..3b7d37a 100644 --- a/src/util/util/lambert-server/Server.ts +++ b/src/util/util/lambert-server/Server.ts @@ -9,138 +9,138 @@ import chalk from "chalk"; declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - interface Request { - server: Server; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + server: Server; + } + } } export type ServerOptions = { - port: number; - host: string; - production: boolean; - serverInitLogging: boolean; - // errorHandler?: { (err: Error, req: Request, res: Response, next: NextFunction): any }; - jsonBody: boolean; - server: http.Server; - app: Application; + port: number; + host: string; + production: boolean; + serverInitLogging: boolean; + // errorHandler?: { (err: Error, req: Request, res: Response, next: NextFunction): any }; + jsonBody: boolean; + server: http.Server; + app: Application; }; // Overwrite default options for Router with default value true for mergeParams const oldRouter = express.Router; express.Router = function (options?: express.RouterOptions | undefined): Router { - if (!options) options = {}; - if (options.mergeParams == null) options.mergeParams = true; + if (!options) options = {}; + if (options.mergeParams == null) options.mergeParams = true; - return oldRouter(options); + return oldRouter(options); }; export class Server { - public app: Application; - public http: HTTPServer; - public options: ServerOptions; - public routes: Router[]; + public app: Application; + public http: HTTPServer; + public options: ServerOptions; + public routes: Router[]; - constructor(opts?: Partial<ServerOptions>) { - if (!opts) opts = {}; - if (!opts.port) opts.port = 8080; - if (!opts.host) opts.host = "0.0.0.0"; - if (opts.production == null) opts.production = false; - if (opts.serverInitLogging == null) opts.serverInitLogging = true; - // if (opts.errorHandler == null) opts.errorHandler = this.errorHandler; - if (opts.jsonBody == null) opts.jsonBody = true; - if (opts.server) this.http = opts.server; + constructor(opts?: Partial<ServerOptions>) { + if (!opts) opts = {}; + if (!opts.port) opts.port = 8080; + if (!opts.host) opts.host = "0.0.0.0"; + if (opts.production == null) opts.production = false; + if (opts.serverInitLogging == null) opts.serverInitLogging = true; + // if (opts.errorHandler == null) opts.errorHandler = this.errorHandler; + if (opts.jsonBody == null) opts.jsonBody = true; + if (opts.server) this.http = opts.server; - this.options = <ServerOptions>opts; + this.options = <ServerOptions>opts; - if (opts.app) this.app = opts.app; - else this.app = express(); - } + if (opts.app) this.app = opts.app; + else this.app = express(); + } - // protected secureExpress() { - // this.app.use(helmet.contentSecurityPolicy()); - // this.app.use(helmet.expectCt); - // this.app.use(helmet.originAgentCluster()); - // this.app.use(helmet.referrerPolicy({ policy: "same-origin" })); - // this.app.use(helmet.hidePoweredBy()); - // this.app.use(helmet.noSniff()); - // this.app.use(helmet.dnsPrefetchControl({ allow: true })); - // this.app.use(helmet.ieNoOpen()); - // this.app.use(helmet.frameguard({ action: "deny" })); - // this.app.use(helmet.permittedCrossDomainPolicies({ permittedPolicies: "none" })); - // } + // protected secureExpress() { + // this.app.use(helmet.contentSecurityPolicy()); + // this.app.use(helmet.expectCt); + // this.app.use(helmet.originAgentCluster()); + // this.app.use(helmet.referrerPolicy({ policy: "same-origin" })); + // this.app.use(helmet.hidePoweredBy()); + // this.app.use(helmet.noSniff()); + // this.app.use(helmet.dnsPrefetchControl({ allow: true })); + // this.app.use(helmet.ieNoOpen()); + // this.app.use(helmet.frameguard({ action: "deny" })); + // this.app.use(helmet.permittedCrossDomainPolicies({ permittedPolicies: "none" })); + // } - public errorHandler = (error: Error, req: Request, res: Response, next: NextFunction) => { - try { - let code; - let message = error?.toString(); + public errorHandler = (error: Error, req: Request, res: Response, next: NextFunction) => { + try { + let code; + let message = error?.toString(); - if (error instanceof HTTPError && error.code) code = error.code || 400; - else { - console.error(error); - if (this.options.production) { - message = "Internal Server Error"; - } - code = 500; - } + if (error instanceof HTTPError && error.code) code = error.code || 400; + else { + console.error(error); + if (this.options.production) { + message = "Internal Server Error"; + } + code = 500; + } - res.status(code).json({ success: false, code: code, error: true, message }); - } catch (e) { - console.error(e); - return res.status(500).json({ success: false, code: 500, error: true, message: "Internal Server Error" }); - } - }; + res.status(code).json({ success: false, code: code, error: true, message }); + } catch (e) { + console.error(e); + return res.status(500).json({ success: false, code: 500, error: true, message: "Internal Server Error" }); + } + }; - async start() { - const server = this.http || this.app; - if (!server.listening) { - await new Promise<void>((res) => { - this.http = server.listen(this.options.port, () => res()); - }); - if (this.options.serverInitLogging) console.log(`[Server] started on ${this.options.host}:${this.options.port}`); - } - } + async start() { + const server = this.http || this.app; + if (!server.listening) { + await new Promise<void>((res) => { + this.http = server.listen(this.options.port, () => res()); + }); + if (this.options.serverInitLogging) console.log(`[Server] started on ${this.options.host}:${this.options.port}`); + } + } - async registerRoutes(root: string) { - this.app.use((req, res, next) => { - req.server = this; - next(); - }); - if (this.options.jsonBody) this.app.use(bodyParser.json()); - const result = await traverseDirectory({ dirname: root, recursive: true }, this.registerRoute.bind(this, root)); - // if (this.options.errorHandler) this.app.use(this.options.errorHandler); - // if (this.options.production) this.secureExpress(); - return result; - } + async registerRoutes(root: string) { + this.app.use((req, res, next) => { + req.server = this; + next(); + }); + if (this.options.jsonBody) this.app.use(bodyParser.json()); + const result = await traverseDirectory({ dirname: root, recursive: true }, this.registerRoute.bind(this, root)); + // if (this.options.errorHandler) this.app.use(this.options.errorHandler); + // if (this.options.production) this.secureExpress(); + return result; + } - registerRoute(root: string, file: string): Router | undefined { - if (root.endsWith("/") || root.endsWith("\\")) root = root.slice(0, -1); // removes slash at the end of the root dir - let path = file.replace(root, ""); // remove root from path and - path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path - path = path.replaceAll("#", ":").replaceAll("!", "?").replaceAll("\\", "/"); - if (path.endsWith("/index")) path = path.slice(0, -6); // delete index from path - if (!path.length) path = "/"; // first root index.js file must have a / path + registerRoute(root: string, file: string): Router | undefined { + if (root.endsWith("/") || root.endsWith("\\")) root = root.slice(0, -1); // removes slash at the end of the root dir + let path = file.replace(root, ""); // remove root from path and + path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path + path = path.replaceAll("#", ":").replaceAll("!", "?").replaceAll("\\", "/"); + if (path.endsWith("/index")) path = path.slice(0, -6); // delete index from path + if (!path.length) path = "/"; // first root index.js file must have a / path - try { - let router = require(file); - if (router.router) router = router.router; - if (router.default) router = router.default; - if (!router || router?.prototype?.constructor?.name !== "router") throw `File doesn't export any default router`; + try { + let router = require(file); + if (router.router) router = router.router; + if (router.default) router = router.default; + if (!router || router?.prototype?.constructor?.name !== "router") throw `File doesn't export any default router`; - // if (this.options.errorHandler) router.use(this.options.errorHandler); - this.app.use(path, <Router>router); + // if (this.options.errorHandler) router.use(this.options.errorHandler); + this.app.use(path, <Router>router); - if (this.options.serverInitLogging) console.log(`[Server] Route ${path} registered`); + if (this.options.serverInitLogging) console.log(`[Server] Route ${path} registered`); - return router; - } catch (error) { - console.error(new Error(`[Server] Failed to register route ${path}: ${error}`)); - } - } + return router; + } catch (error) { + console.error(new Error(`[Server] Failed to register route ${path}: ${error}`)); + } + } - stop() { - return new Promise<void>((res) => this.http.close(() => res())); - } + stop() { + return new Promise<void>((res) => this.http.close(() => res())); + } } diff --git a/src/util/util/lambert-server/Utils.ts b/src/util/util/lambert-server/Utils.ts index 0785e85..32f4b16 100644 --- a/src/util/util/lambert-server/Utils.ts +++ b/src/util/util/lambert-server/Utils.ts @@ -1,36 +1,36 @@ import fs from "fs"; export interface traverseDirectoryOptions { - dirname: string; - filter?: RegExp; - excludeDirs?: RegExp; - recursive?: boolean; + dirname: string; + filter?: RegExp; + excludeDirs?: RegExp; + recursive?: boolean; } const DEFAULT_EXCLUDE_DIR = /^\./; const DEFAULT_FILTER = /^([^.].*)(?<!\.d)\.(ts|js)$/; export async function traverseDirectory<T>(options: traverseDirectoryOptions, action: (path: string) => T): Promise<T[]> { - if (!options.filter) options.filter = DEFAULT_FILTER; - if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR; + if (!options.filter) options.filter = DEFAULT_FILTER; + if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR; - const routes = fs.readdirSync(options.dirname); - const promises = <Promise<T | T[] | undefined>[]>routes - .sort((a, b) => (a.startsWith("#") ? 1 : -1)) // load #parameter routes last - .map(async (file) => { - const path = options.dirname + file; - const stat = fs.lstatSync(path); - if (path.match(<RegExp>options.excludeDirs)) return; + const routes = fs.readdirSync(options.dirname); + const promises = <Promise<T | T[] | undefined>[]>routes + .sort((a, b) => (a.startsWith("#") ? 1 : -1)) // load #parameter routes last + .map(async (file) => { + const path = options.dirname + file; + const stat = fs.lstatSync(path); + if (path.match(<RegExp>options.excludeDirs)) return; - if (path.match(<RegExp>options.filter) && stat.isFile()) { - return action(path); - } else if (options.recursive && stat.isDirectory()) { - return traverseDirectory({ ...options, dirname: path + "/" }, action); - } - }); - const result = await Promise.all(promises); + if (path.match(<RegExp>options.filter) && stat.isFile()) { + return action(path); + } else if (options.recursive && stat.isDirectory()) { + return traverseDirectory({ ...options, dirname: path + "/" }, action); + } + }); + const result = await Promise.all(promises); - const t = <(T | undefined)[]>result.flat(); + const t = <(T | undefined)[]>result.flat(); - return <T[]>t.filter((x) => x != undefined); + return <T[]>t.filter((x) => x != undefined); } diff --git a/src/util/util/lambert-server/check.ts b/src/util/util/lambert-server/check.ts index 5fe9d84..864e5fa 100644 --- a/src/util/util/lambert-server/check.ts +++ b/src/util/util/lambert-server/check.ts @@ -5,109 +5,109 @@ const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export class Tuple { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public types: any[]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - constructor(...types: any[]) { - this.types = types; - } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public types: any[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(...types: any[]) { + this.types = types; + } } export class Email { - constructor(public email: string) {} - check() { - return !!this.email.match(EMAIL_REGEX); - } + constructor(public email: string) {} + check() { + return !!this.email.match(EMAIL_REGEX); + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function instanceOf(type: any, value: any, { path = "", optional = false }: { path?: string; optional?: boolean } = {}): boolean { - if (!type) return true; // no type was specified + if (!type) return true; // no type was specified - if (value == null) { - if (optional) return true; - throw `${path} is required`; - } + if (value == null) { + if (optional) return true; + throw `${path} is required`; + } - switch (type) { - case String: - if (typeof value === "string") return true; - throw `${path} must be a string`; - case Number: - value = Number(value); - if (typeof value === "number" && !isNaN(value)) return true; - throw `${path} must be a number`; - case BigInt: - try { - value = BigInt(value); - if (typeof value === "bigint") return true; - } catch (error) { - //Ignore BigInt error - } - throw `${path} must be a bigint`; - case Boolean: - if (value == "true") value = true; - if (value == "false") value = false; - if (typeof value === "boolean") return true; - throw `${path} must be a boolean`; - case Object: - if (typeof value === "object" && value !== null) return true; - throw `${path} must be a object`; - } + switch (type) { + case String: + if (typeof value === "string") return true; + throw `${path} must be a string`; + case Number: + value = Number(value); + if (typeof value === "number" && !isNaN(value)) return true; + throw `${path} must be a number`; + case BigInt: + try { + value = BigInt(value); + if (typeof value === "bigint") return true; + } catch (error) { + //Ignore BigInt error + } + throw `${path} must be a bigint`; + case Boolean: + if (value == "true") value = true; + if (value == "false") value = false; + if (typeof value === "boolean") return true; + throw `${path} must be a boolean`; + case Object: + if (typeof value === "object" && value !== null) return true; + throw `${path} must be a object`; + } - if (typeof type === "object") { - if (Array.isArray(type)) { - if (!Array.isArray(value)) throw `${path} must be an array`; - if (!type.length) return true; // type array didn't specify any type + if (typeof type === "object") { + if (Array.isArray(type)) { + if (!Array.isArray(value)) throw `${path} must be an array`; + if (!type.length) return true; // type array didn't specify any type - return value.every((val, i) => instanceOf(type[0], val, { path: `${path}[${i}]`, optional })); - } - if (type?.constructor?.name != "Object") { - if (type instanceof Tuple) { - if ( - (<Tuple>type).types.some((x) => { - try { - return instanceOf(x, value, { path, optional }); - } catch (error) { - return false; - } - }) - ) { - return true; - } - throw `${path} must be one of ${type.types}`; - } - if (type instanceof Email) { - if ((<Email>type).check()) return true; - throw `${path} is not a valid E-Mail`; - } - if (value instanceof type) return true; - throw `${path} must be an instance of ${type}`; - } - if (typeof value !== "object") throw `${path} must be a object`; + return value.every((val, i) => instanceOf(type[0], val, { path: `${path}[${i}]`, optional })); + } + if (type?.constructor?.name != "Object") { + if (type instanceof Tuple) { + if ( + (<Tuple>type).types.some((x) => { + try { + return instanceOf(x, value, { path, optional }); + } catch (error) { + return false; + } + }) + ) { + return true; + } + throw `${path} must be one of ${type.types}`; + } + if (type instanceof Email) { + if ((<Email>type).check()) return true; + throw `${path} is not a valid E-Mail`; + } + if (value instanceof type) return true; + throw `${path} must be an instance of ${type}`; + } + if (typeof value !== "object") throw `${path} must be a object`; - const filterset = new Set(Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x))); - const diff = Object.keys(value).filter((_) => !filterset.has(_)); + const filterset = new Set(Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x))); + const diff = Object.keys(value).filter((_) => !filterset.has(_)); - if (diff.length) throw `Unknown key ${diff}`; + if (diff.length) throw `Unknown key ${diff}`; - return Object.keys(type).every((key) => { - let newKey = key; - const OPTIONAL = key.startsWith(OPTIONAL_PREFIX); - if (OPTIONAL) newKey = newKey.slice(OPTIONAL_PREFIX.length); + return Object.keys(type).every((key) => { + let newKey = key; + const OPTIONAL = key.startsWith(OPTIONAL_PREFIX); + if (OPTIONAL) newKey = newKey.slice(OPTIONAL_PREFIX.length); - return instanceOf(type[key], value[newKey], { - path: `${path}.${newKey}`, - optional: OPTIONAL, - }); - }); - } else if (typeof type === "number" || typeof type === "string" || typeof type === "boolean") { - if (value === type) return true; - throw `${path} must be ${value}`; - } else if (typeof type === "bigint") { - if (BigInt(value) === type) return true; - throw `${path} must be ${value}`; - } + return instanceOf(type[key], value[newKey], { + path: `${path}.${newKey}`, + optional: OPTIONAL, + }); + }); + } else if (typeof type === "number" || typeof type === "string" || typeof type === "boolean") { + if (value === type) return true; + throw `${path} must be ${value}`; + } else if (typeof type === "bigint") { + if (BigInt(value) === type) return true; + throw `${path} must be ${value}`; + } - return type == value; + return type == value; } diff --git a/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts b/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts index 5560fa8..c4b69a4 100644 --- a/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts +++ b/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts @@ -2,64 +2,64 @@ import { AbuseIpDbBlacklistResponse, AbuseIpDbCheckResponse } from "./AbuseIpDbSampleResponses"; export class AbuseIpDbClient { - private static ipCheckCache: Map< - string, - { - data: AbuseIpDbCheckResponse; - expires: number; - } - > = new Map(); + private static ipCheckCache: Map< + string, + { + data: AbuseIpDbCheckResponse; + expires: number; + } + > = new Map(); - private static blacklistCache: { - data: AbuseIpDbBlacklistResponse; - expires: number; - } | null = null; + private static blacklistCache: { + data: AbuseIpDbBlacklistResponse; + expires: number; + } | null = null; - static async checkIpAddress(ip: string): Promise<AbuseIpDbCheckResponse | null> { - const { ipdataApiKey } = Config.get().security; - if (!ipdataApiKey) return null; - if (this.ipCheckCache.get(ip)?.expires ?? 0 > Date.now()) return this.ipCheckCache.get(ip)!.data; + static async checkIpAddress(ip: string): Promise<AbuseIpDbCheckResponse | null> { + const { ipdataApiKey } = Config.get().security; + if (!ipdataApiKey) return null; + if (this.ipCheckCache.get(ip)?.expires ?? 0 > Date.now()) return this.ipCheckCache.get(ip)!.data; - console.log(`[AbuseIPDB] Checking IP address ${ip}...`); - const resp = (await (await fetch(`https://api.abuseipdb.com/api/v2/check?ipAddress=${ip}`)).json()) as Promise<AbuseIpDbCheckResponse>; - this.ipCheckCache.set(ip, { - data: await resp, - expires: new DateBuilder().addHours(12).buildTimestamp(), - }); - return await resp; - } + console.log(`[AbuseIPDB] Checking IP address ${ip}...`); + const resp = (await (await fetch(`https://api.abuseipdb.com/api/v2/check?ipAddress=${ip}`)).json()) as Promise<AbuseIpDbCheckResponse>; + this.ipCheckCache.set(ip, { + data: await resp, + expires: new DateBuilder().addHours(12).buildTimestamp(), + }); + return await resp; + } - static async getBlacklist(): Promise<AbuseIpDbBlacklistResponse | null> { - const { abuseIpDbApiKey, abuseipdbBlacklistRatelimit } = Config.get().security; - if (!abuseIpDbApiKey) return null; - if (this.blacklistCache?.expires ?? 0 > Date.now()) return this.blacklistCache!.data; + static async getBlacklist(): Promise<AbuseIpDbBlacklistResponse | null> { + const { abuseIpDbApiKey, abuseipdbBlacklistRatelimit } = Config.get().security; + if (!abuseIpDbApiKey) return null; + if (this.blacklistCache?.expires ?? 0 > Date.now()) return this.blacklistCache!.data; - console.log("[AbuseIPDB] Fetching blacklist..."); - const resp = (await ( - await fetch(`https://api.abuseipdb.com/api/v2/blacklist`, { - headers: { - Key: abuseIpDbApiKey, - Accept: "application/json", - }, - }) - ).json()) as Promise<AbuseIpDbBlacklistResponse>; + console.log("[AbuseIPDB] Fetching blacklist..."); + const resp = (await ( + await fetch(`https://api.abuseipdb.com/api/v2/blacklist`, { + headers: { + Key: abuseIpDbApiKey, + Accept: "application/json", + }, + }) + ).json()) as Promise<AbuseIpDbBlacklistResponse>; - this.blacklistCache = { - data: await resp, - expires: new DateBuilder().addHours(Math.ceil(24 / abuseipdbBlacklistRatelimit)).buildTimestamp(), - }; + this.blacklistCache = { + data: await resp, + expires: new DateBuilder().addHours(Math.ceil(24 / abuseipdbBlacklistRatelimit)).buildTimestamp(), + }; - return await resp; - } + return await resp; + } - static async isIpBlacklisted(ip: string): Promise<boolean> { - const { abuseipdbConfidenceScoreTreshold } = Config.get().security; - const blacklist = await this.getBlacklist(); - if (!blacklist) return false; + static async isIpBlacklisted(ip: string): Promise<boolean> { + const { abuseipdbConfidenceScoreTreshold } = Config.get().security; + const blacklist = await this.getBlacklist(); + if (!blacklist) return false; - const entry = blacklist.data.find((x) => x.ipAddress === ip); - if (!entry) return false; + const entry = blacklist.data.find((x) => x.ipAddress === ip); + if (!entry) return false; - return entry.abuseConfidenceScore >= abuseipdbConfidenceScoreTreshold; - } + return entry.abuseConfidenceScore >= abuseipdbConfidenceScoreTreshold; + } } diff --git a/src/util/util/networking/abuseipdb/AbuseIpDbSampleResponses.ts b/src/util/util/networking/abuseipdb/AbuseIpDbSampleResponses.ts index 210af2c..c6cf556 100644 --- a/src/util/util/networking/abuseipdb/AbuseIpDbSampleResponses.ts +++ b/src/util/util/networking/abuseipdb/AbuseIpDbSampleResponses.ts @@ -20,54 +20,54 @@ export type AbuseIpDbBlacklistResponse = typeof abuseIpDbBlacklistResponseSample; const abuseIpDbCheckResponseSample = { - data: { - ipAddress: "118.25.6.39", - isPublic: true, - ipVersion: 4, - isWhitelisted: false, - abuseConfidenceScore: 100, - countryCode: "CN", - countryName: "China", - usageType: "Data Center/Web Hosting/Transit", - isp: "Tencent Cloud Computing (Beijing) Co. Ltd", - domain: "tencent.com", - hostnames: [], - isTor: false, - totalReports: 1, - numDistinctUsers: 1, - lastReportedAt: "2018-12-20T20:55:14+00:00", - reports: [ - { - reportedAt: "2018-12-20T20:55:14+00:00", - comment: "Dec 20 20:55:14 srv206 sshd[13937]: Invalid user oracle from 118.25.6.39", - categories: [18, 22], - reporterId: 1, - reporterCountryCode: "US", - reporterCountryName: "United States", - }, - ], - }, + data: { + ipAddress: "118.25.6.39", + isPublic: true, + ipVersion: 4, + isWhitelisted: false, + abuseConfidenceScore: 100, + countryCode: "CN", + countryName: "China", + usageType: "Data Center/Web Hosting/Transit", + isp: "Tencent Cloud Computing (Beijing) Co. Ltd", + domain: "tencent.com", + hostnames: [], + isTor: false, + totalReports: 1, + numDistinctUsers: 1, + lastReportedAt: "2018-12-20T20:55:14+00:00", + reports: [ + { + reportedAt: "2018-12-20T20:55:14+00:00", + comment: "Dec 20 20:55:14 srv206 sshd[13937]: Invalid user oracle from 118.25.6.39", + categories: [18, 22], + reporterId: 1, + reporterCountryCode: "US", + reporterCountryName: "United States", + }, + ], + }, }; const abuseIpDbBlacklistResponseSample = { - meta: { - generatedAt: "2020-09-24T19:54:11+00:00", - }, - data: [ - { - ipAddress: "5.188.10.179", - abuseConfidenceScore: 100, - lastReportedAt: "2020-09-24T19:17:02+00:00", - }, - { - ipAddress: "185.222.209.14", - abuseConfidenceScore: 100, - lastReportedAt: "2020-09-24T19:17:02+00:00", - }, - { - ipAddress: "191.96.249.183", - abuseConfidenceScore: 100, - lastReportedAt: "2020-09-24T19:17:01+00:00", - }, - ], + meta: { + generatedAt: "2020-09-24T19:54:11+00:00", + }, + data: [ + { + ipAddress: "5.188.10.179", + abuseConfidenceScore: 100, + lastReportedAt: "2020-09-24T19:17:02+00:00", + }, + { + ipAddress: "185.222.209.14", + abuseConfidenceScore: 100, + lastReportedAt: "2020-09-24T19:17:02+00:00", + }, + { + ipAddress: "191.96.249.183", + abuseConfidenceScore: 100, + lastReportedAt: "2020-09-24T19:17:01+00:00", + }, + ], }; diff --git a/src/util/util/networking/ipdata/IpDataClient.ts b/src/util/util/networking/ipdata/IpDataClient.ts index 8e83384..61778ef 100644 --- a/src/util/util/networking/ipdata/IpDataClient.ts +++ b/src/util/util/networking/ipdata/IpDataClient.ts @@ -2,34 +2,34 @@ import { IpDataIpLookupResponse } from "./IpDataSampleResponses"; export class IpDataClient { - private static ipInfoCache: Map< - string, - { - data: IpDataIpLookupResponse; - expires: number; - } - > = new Map(); + private static ipInfoCache: Map< + string, + { + data: IpDataIpLookupResponse; + expires: number; + } + > = new Map(); - static async getIpInfo(ip: string): Promise<IpDataIpLookupResponse | null> { - const { ipdataApiKey } = Config.get().security; - if (!ipdataApiKey) return null; - if (this.ipInfoCache.get(ip)?.expires ?? 0 > Date.now()) return this.ipInfoCache.get(ip)!.data; + static async getIpInfo(ip: string): Promise<IpDataIpLookupResponse | null> { + const { ipdataApiKey } = Config.get().security; + if (!ipdataApiKey) return null; + if (this.ipInfoCache.get(ip)?.expires ?? 0 > Date.now()) return this.ipInfoCache.get(ip)!.data; - console.log(`[IpData] Fetching info for IP address ${ip}...`); - const resp = (await (await fetch(`https://eu-api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json()) as Promise<IpDataIpLookupResponse>; - this.ipInfoCache.set(ip, { - data: await resp, - expires: new DateBuilder().addHours(12).buildTimestamp(), - }); - return await resp; - } + console.log(`[IpData] Fetching info for IP address ${ip}...`); + const resp = (await (await fetch(`https://eu-api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json()) as Promise<IpDataIpLookupResponse>; + this.ipInfoCache.set(ip, { + data: await resp, + expires: new DateBuilder().addHours(12).buildTimestamp(), + }); + return await resp; + } - //TODO add function that support both ip and domain names - static isProxy(data: IpDataIpLookupResponse) { - if (!data || !data.asn || !data.threat) return false; - if (data.asn.type !== "isp") return true; - if (Object.values(data.threat).some((x) => x)) return true; + //TODO add function that support both ip and domain names + static isProxy(data: IpDataIpLookupResponse) { + if (!data || !data.asn || !data.threat) return false; + if (data.asn.type !== "isp") return true; + if (Object.values(data.threat).some((x) => x)) return true; - return false; - } + return false; + } } diff --git a/src/util/util/networking/ipdata/IpDataSampleResponses.ts b/src/util/util/networking/ipdata/IpDataSampleResponses.ts index fa6d0fa..3f182d6 100644 --- a/src/util/util/networking/ipdata/IpDataSampleResponses.ts +++ b/src/util/util/networking/ipdata/IpDataSampleResponses.ts @@ -19,61 +19,61 @@ export type IpDataIpLookupResponse = typeof ipDataSampleIpLookupResponse; const ipDataSampleIpLookupResponse = { - ip: "", - is_eu: true, - city: "", - region: "", - region_code: "", - country_name: "", - country_code: "", - continent_name: "", - continent_code: "", - latitude: 0, - longitude: 0, - postal: "", - calling_code: "", - flag: "", - emoji_flag: "", - emoji_unicode: "", - asn: { - asn: "", - name: "", - domain: "", - route: "", - type: "isp", - }, - languages: [ - { - name: "", - native: "", - }, - ], - currency: { - name: "", - code: "", - symbol: "", - native: "", - plural: "", - }, - time_zone: { - name: "", - abbr: "", - offset: "", - is_dst: true, - current_time: "", - }, - threat: { - is_tor: false, - is_icloud_relay: false, - is_proxy: false, - is_datacenter: false, - is_anonymous: false, - is_known_attacker: false, - is_known_abuser: false, - is_threat: false, - is_bogon: false, - blocklists: [], - }, - count: 0, - status: 200, + ip: "", + is_eu: true, + city: "", + region: "", + region_code: "", + country_name: "", + country_code: "", + continent_name: "", + continent_code: "", + latitude: 0, + longitude: 0, + postal: "", + calling_code: "", + flag: "", + emoji_flag: "", + emoji_unicode: "", + asn: { + asn: "", + name: "", + domain: "", + route: "", + type: "isp", + }, + languages: [ + { + name: "", + native: "", + }, + ], + currency: { + name: "", + code: "", + symbol: "", + native: "", + plural: "", + }, + time_zone: { + name: "", + abbr: "", + offset: "", + is_dst: true, + current_time: "", + }, + threat: { + is_tor: false, + is_icloud_relay: false, + is_proxy: false, + is_datacenter: false, + is_anonymous: false, + is_known_attacker: false, + is_known_abuser: false, + is_threat: false, + is_bogon: false, + blocklists: [], + }, + count: 0, + status: 200, }; diff --git a/src/webrtc/Server.ts b/src/webrtc/Server.ts index a20531d..334b911 100644 --- a/src/webrtc/Server.ts +++ b/src/webrtc/Server.ts @@ -25,62 +25,62 @@ import { green, yellow } from "picocolors"; export class Server { - public ws: ws.Server; - public port: number; - public server: http.Server; - public production: boolean; + public ws: ws.Server; + public port: number; + public server: http.Server; + public production: boolean; - constructor({ port, server, production }: { port: number; server?: http.Server; production?: boolean }) { - this.port = port; - this.production = production || false; + constructor({ port, server, production }: { port: number; server?: http.Server; production?: boolean }) { + this.port = port; + this.production = production || false; - if (server) this.server = server; - else { - this.server = http.createServer(function (req, res) { - res.writeHead(200).end("Online"); - }); - } + if (server) this.server = server; + else { + this.server = http.createServer(function (req, res) { + res.writeHead(200).end("Online"); + }); + } - // this.server.on("upgrade", (request, socket, head) => { - // if (!request.url?.includes("voice")) return; - // this.ws.handleUpgrade(request, socket, head, (socket) => { - // // @ts-ignore - // socket.server = this; - // this.ws.emit("connection", socket, request); - // }); - // }); + // this.server.on("upgrade", (request, socket, head) => { + // if (!request.url?.includes("voice")) return; + // this.ws.handleUpgrade(request, socket, head, (socket) => { + // // @ts-ignore + // socket.server = this; + // this.ws.emit("connection", socket, request); + // }); + // }); - this.ws = new ws.Server({ - maxPayload: 1024 * 1024 * 100, - server: this.server, - }); - this.ws.on("connection", Connection); - this.ws.on("error", console.error); - } + this.ws = new ws.Server({ + maxPayload: 1024 * 1024 * 100, + server: this.server, + }); + this.ws.on("connection", Connection); + this.ws.on("error", console.error); + } - async start(): Promise<void> { - await initDatabase(); - await Config.init(); - await initEvent(); + async start(): Promise<void> { + await initDatabase(); + await Config.init(); + await initEvent(); - // try to load webrtc library, if failed just don't start webrtc endpoint - try { - await loadWebRtcLibrary(); - } catch (e) { - console.log(`[WebRTC] ${yellow("WEBRTC disabled")}`); - return; - } + // try to load webrtc library, if failed just don't start webrtc endpoint + try { + await loadWebRtcLibrary(); + } catch (e) { + console.log(`[WebRTC] ${yellow("WEBRTC disabled")}`); + return; + } - await mediaServer.start(WRTC_PUBLIC_IP, WRTC_PORT_MIN, WRTC_PORT_MAX); - if (!this.server.listening) { - this.server.listen(this.port); - console.log(`[WebRTC] ${green(`online on 0.0.0.0:${this.port}`)}`); - } - } + await mediaServer.start(WRTC_PUBLIC_IP, WRTC_PORT_MIN, WRTC_PORT_MAX); + if (!this.server.listening) { + this.server.listen(this.port); + console.log(`[WebRTC] ${green(`online on 0.0.0.0:${this.port}`)}`); + } + } - async stop() { - closeDatabase(); - this.server.close(); - mediaServer?.stop(); - } + async stop() { + closeDatabase(); + this.server.close(); + mediaServer?.stop(); + } } diff --git a/src/webrtc/events/Close.ts b/src/webrtc/events/Close.ts index 0419a70..e88b293 100644 --- a/src/webrtc/events/Close.ts +++ b/src/webrtc/events/Close.ts @@ -19,7 +19,7 @@ import { WebSocket } from "@spacebar/gateway"; export async function onClose(this: WebSocket, code: number, reason: string) { - console.log("[WebRTC] closed", code, reason.toString()); + console.log("[WebRTC] closed", code, reason.toString()); - this.removeAllListeners(); + this.removeAllListeners(); } diff --git a/src/webrtc/events/Connection.ts b/src/webrtc/events/Connection.ts index 6e028c6..b31b899 100644 --- a/src/webrtc/events/Connection.ts +++ b/src/webrtc/events/Connection.ts @@ -29,46 +29,46 @@ // TODO: check msg max size export async function Connection(this: WS.Server, socket: WebRtcWebSocket, request: IncomingMessage) { - try { - socket.on("close", onClose.bind(socket)); - socket.on("message", onMessage.bind(socket)); - console.log("[WebRTC] new connection", request.url); + try { + socket.on("close", onClose.bind(socket)); + socket.on("message", onMessage.bind(socket)); + console.log("[WebRTC] new connection", request.url); - if (process.env.WS_LOGEVENTS) { - [ - "close", - "error", - "upgrade", - //"message", - "open", - "ping", - "pong", - "unexpected-response", - ].forEach((x) => { - socket.on(x, (y) => console.log("[WebRTC]", x, y)); - }); - } + if (process.env.WS_LOGEVENTS) { + [ + "close", + "error", + "upgrade", + //"message", + "open", + "ping", + "pong", + "unexpected-response", + ].forEach((x) => { + socket.on(x, (y) => console.log("[WebRTC]", x, y)); + }); + } - const { searchParams } = new URL(`http://localhost${request.url}`); + const { searchParams } = new URL(`http://localhost${request.url}`); - socket.encoding = "json"; - socket.version = Number(searchParams.get("v")) || 5; - if (socket.version < 3) return socket.close(CLOSECODES.Unknown_error, "invalid version"); + socket.encoding = "json"; + socket.version = Number(searchParams.get("v")) || 5; + if (socket.version < 3) return socket.close(CLOSECODES.Unknown_error, "invalid version"); - setHeartbeat(socket); + setHeartbeat(socket); - socket.readyTimeout = setTimeout(() => { - return socket.close(CLOSECODES.Session_timed_out); - }, 1000 * 30); + socket.readyTimeout = setTimeout(() => { + return socket.close(CLOSECODES.Session_timed_out); + }, 1000 * 30); - await Send(socket, { - op: VoiceOPCodes.HELLO, - d: { - heartbeat_interval: 1000 * 30, - }, - }); - } catch (error) { - console.error("[WebRTC]", error); - return socket.close(CLOSECODES.Unknown_error); - } + await Send(socket, { + op: VoiceOPCodes.HELLO, + d: { + heartbeat_interval: 1000 * 30, + }, + }); + } catch (error) { + console.error("[WebRTC]", error); + return socket.close(CLOSECODES.Unknown_error); + } } diff --git a/src/webrtc/events/Message.ts b/src/webrtc/events/Message.ts index 9325107..461d76e 100644 --- a/src/webrtc/events/Message.ts +++ b/src/webrtc/events/Message.ts @@ -22,32 +22,32 @@ import { VoiceOPCodes, VoicePayload, WebRtcWebSocket } from "../util"; const PayloadSchema = { - op: Number, - $d: new Tuple(Object, Number), // or number for heartbeat sequence - $s: Number, - $t: String, + op: Number, + $d: new Tuple(Object, Number), // or number for heartbeat sequence + $s: Number, + $t: String, }; export async function onMessage(this: WebRtcWebSocket, buffer: Buffer) { - try { - const data: VoicePayload = JSON.parse(buffer.toString()); - if (data.op !== VoiceOPCodes.IDENTIFY && !this.user_id) return this.close(CLOSECODES.Not_authenticated); + try { + const data: VoicePayload = JSON.parse(buffer.toString()); + if (data.op !== VoiceOPCodes.IDENTIFY && !this.user_id) return this.close(CLOSECODES.Not_authenticated); - const OPCodeHandler = OPCodeHandlers[data.op]; - if (!OPCodeHandler) { - console.error("[WebRTC] Unknown opcode " + VoiceOPCodes[data.op]); - // TODO: if all opcodes are implemented comment this out: - // this.close(CloseCodes.Unknown_opcode); - return; - } + const OPCodeHandler = OPCodeHandlers[data.op]; + if (!OPCodeHandler) { + console.error("[WebRTC] Unknown opcode " + VoiceOPCodes[data.op]); + // TODO: if all opcodes are implemented comment this out: + // this.close(CloseCodes.Unknown_opcode); + return; + } - if (![VoiceOPCodes.HEARTBEAT, VoiceOPCodes.SPEAKING].includes(data.op as VoiceOPCodes)) { - console.log("[WebRTC] Opcode " + VoiceOPCodes[data.op]); - } + if (![VoiceOPCodes.HEARTBEAT, VoiceOPCodes.SPEAKING].includes(data.op as VoiceOPCodes)) { + console.log("[WebRTC] Opcode " + VoiceOPCodes[data.op]); + } - return await OPCodeHandler.call(this, data); - } catch (error) { - console.error("[WebRTC] error", error); - // if (!this.CLOSED && this.CLOSING) return this.close(CloseCodes.Unknown_error); - } + return await OPCodeHandler.call(this, data); + } catch (error) { + console.error("[WebRTC] error", error); + // if (!this.CLOSED && this.CLOSING) return this.close(CloseCodes.Unknown_error); + } } diff --git a/src/webrtc/opcodes/BackendVersion.ts b/src/webrtc/opcodes/BackendVersion.ts index 556e55f..d9172a7 100644 --- a/src/webrtc/opcodes/BackendVersion.ts +++ b/src/webrtc/opcodes/BackendVersion.ts @@ -19,8 +19,8 @@ import { VoiceOPCodes, VoicePayload, WebRtcWebSocket, Send } from "../util"; export async function onBackendVersion(this: WebRtcWebSocket, data: VoicePayload) { - await Send(this, { - op: VoiceOPCodes.VOICE_BACKEND_VERSION, - d: { voice: "0.8.43", rtc_worker: "0.3.26" }, - }); + await Send(this, { + op: VoiceOPCodes.VOICE_BACKEND_VERSION, + d: { voice: "0.8.43", rtc_worker: "0.3.26" }, + }); } diff --git a/src/webrtc/opcodes/Heartbeat.ts b/src/webrtc/opcodes/Heartbeat.ts index ef3cae4..ee0e72b 100644 --- a/src/webrtc/opcodes/Heartbeat.ts +++ b/src/webrtc/opcodes/Heartbeat.ts @@ -20,8 +20,8 @@ import { VoiceOPCodes, VoicePayload, WebRtcWebSocket, Send } from "../util"; export async function onHeartbeat(this: WebRtcWebSocket, data: VoicePayload) { - setHeartbeat(this); - if (isNaN(data.d)) return this.close(CLOSECODES.Decode_error); + setHeartbeat(this); + if (isNaN(data.d)) return this.close(CLOSECODES.Decode_error); - await Send(this, { op: VoiceOPCodes.HEARTBEAT_ACK, d: data.d }); + await Send(this, { op: VoiceOPCodes.HEARTBEAT_ACK, d: data.d }); } diff --git a/src/webrtc/opcodes/Identify.ts b/src/webrtc/opcodes/Identify.ts index 8a2516c..ac93011 100644 --- a/src/webrtc/opcodes/Identify.ts +++ b/src/webrtc/opcodes/Identify.ts @@ -24,103 +24,103 @@ import { subscribeToProducers } from "./Video"; export async function onIdentify(this: WebRtcWebSocket, data: VoicePayload) { - clearTimeout(this.readyTimeout); - const { server_id, user_id, session_id, token, streams, video } = validateSchema("VoiceIdentifySchema", data.d) as VoiceIdentifySchema; + clearTimeout(this.readyTimeout); + const { server_id, user_id, session_id, token, streams, video } = validateSchema("VoiceIdentifySchema", data.d) as VoiceIdentifySchema; - // server_id can be one of the following: a unique id for a GO Live stream, a channel id for a DM voice call, or a guild id for a guild voice channel - // not sure if there's a way to determine whether a snowflake is a channel id or a guild id without checking if it exists in db - // luckily we will only have to determine this once - let type: "guild-voice" | "dm-voice" | "stream" = "guild-voice"; - let authenticated = false; + // server_id can be one of the following: a unique id for a GO Live stream, a channel id for a DM voice call, or a guild id for a guild voice channel + // not sure if there's a way to determine whether a snowflake is a channel id or a guild id without checking if it exists in db + // luckily we will only have to determine this once + let type: "guild-voice" | "dm-voice" | "stream" = "guild-voice"; + let authenticated = false; - // first check if its a guild voice connection or DM voice call - const voiceState = await VoiceState.findOne({ - where: [ - { guild_id: server_id, user_id, token, session_id }, - { channel_id: server_id, user_id, token, session_id }, - ], - }); + // first check if its a guild voice connection or DM voice call + const voiceState = await VoiceState.findOne({ + where: [ + { guild_id: server_id, user_id, token, session_id }, + { channel_id: server_id, user_id, token, session_id }, + ], + }); - if (voiceState) { - type = voiceState.guild_id === server_id ? "guild-voice" : "dm-voice"; - authenticated = true; - } else { - // if its not a guild/dm voice connection, check if it is a go live stream - const streamSession = await StreamSession.findOne({ - where: { - stream_id: server_id, - user_id, - token, - session_id, - used: false, - }, - relations: ["stream"], - }); + if (voiceState) { + type = voiceState.guild_id === server_id ? "guild-voice" : "dm-voice"; + authenticated = true; + } else { + // if its not a guild/dm voice connection, check if it is a go live stream + const streamSession = await StreamSession.findOne({ + where: { + stream_id: server_id, + user_id, + token, + session_id, + used: false, + }, + relations: ["stream"], + }); - if (streamSession) { - type = "stream"; - authenticated = true; - streamSession.used = true; - await streamSession.save(); + if (streamSession) { + type = "stream"; + authenticated = true; + streamSession.used = true; + await streamSession.save(); - this.once("close", async () => { - await streamSession.remove(); - }); - } - } + this.once("close", async () => { + await streamSession.remove(); + }); + } + } - // if it doesnt match any then not valid token - if (!authenticated) return this.close(CLOSECODES.Authentication_failed); + // if it doesnt match any then not valid token + if (!authenticated) return this.close(CLOSECODES.Authentication_failed); - this.user_id = user_id; - this.session_id = session_id; + this.user_id = user_id; + this.session_id = session_id; - this.type = type; + this.type = type; - const voiceRoomId = type === "stream" ? server_id : voiceState!.channel_id; - this.webRtcClient = await mediaServer.join(voiceRoomId, this.user_id, this, type!); + const voiceRoomId = type === "stream" ? server_id : voiceState!.channel_id; + this.webRtcClient = await mediaServer.join(voiceRoomId, this.user_id, this, type!); - this.on("close", () => { - // ice-lite media server relies on this to know when the peer went away - mediaServer.onClientClose(this.webRtcClient!); - }); + this.on("close", () => { + // ice-lite media server relies on this to know when the peer went away + mediaServer.onClientClose(this.webRtcClient!); + }); - // once connected subscribe to tracks from other users - this.webRtcClient.emitter.once("connected", async () => { - await subscribeToProducers.call(this); - }); + // once connected subscribe to tracks from other users + this.webRtcClient.emitter.once("connected", async () => { + await subscribeToProducers.call(this); + }); - // the server generates a unique ssrc for the audio and video stream. Must be unique among users connected to same server - // UDP clients will respect this ssrc, but websocket clients will generate and replace it with their own - const generatedSsrc: SSRCs = { - audio_ssrc: generateSsrc(), - video_ssrc: generateSsrc(), - rtx_ssrc: generateSsrc(), - }; - this.webRtcClient.initIncomingSSRCs(generatedSsrc); + // the server generates a unique ssrc for the audio and video stream. Must be unique among users connected to same server + // UDP clients will respect this ssrc, but websocket clients will generate and replace it with their own + const generatedSsrc: SSRCs = { + audio_ssrc: generateSsrc(), + video_ssrc: generateSsrc(), + rtx_ssrc: generateSsrc(), + }; + this.webRtcClient.initIncomingSSRCs(generatedSsrc); - await Send(this, { - op: VoiceOPCodes.READY, - d: { - ssrc: generatedSsrc.audio_ssrc, - port: mediaServer.port, - modes: [ - "aead_aes256_gcm_rtpsize", - "aead_aes256_gcm", - "aead_xchacha20_poly1305_rtpsize", - "xsalsa20_poly1305_lite_rtpsize", - "xsalsa20_poly1305_lite", - "xsalsa20_poly1305_suffix", - "xsalsa20_poly1305", - ], - ip: mediaServer.ip, - experiments: [], - streams: streams?.map((x) => ({ - ...x, - ssrc: generatedSsrc.video_ssrc, - rtx_ssrc: generatedSsrc.rtx_ssrc, - type: "video", // client expects this to be overriden for some reason??? - })), - }, - }); + await Send(this, { + op: VoiceOPCodes.READY, + d: { + ssrc: generatedSsrc.audio_ssrc, + port: mediaServer.port, + modes: [ + "aead_aes256_gcm_rtpsize", + "aead_aes256_gcm", + "aead_xchacha20_poly1305_rtpsize", + "xsalsa20_poly1305_lite_rtpsize", + "xsalsa20_poly1305_lite", + "xsalsa20_poly1305_suffix", + "xsalsa20_poly1305", + ], + ip: mediaServer.ip, + experiments: [], + streams: streams?.map((x) => ({ + ...x, + ssrc: generatedSsrc.video_ssrc, + rtx_ssrc: generatedSsrc.rtx_ssrc, + type: "video", // client expects this to be overriden for some reason??? + })), + }, + }); } diff --git a/src/webrtc/opcodes/SelectProtocol.ts b/src/webrtc/opcodes/SelectProtocol.ts index a29bcac..6a73393 100644 --- a/src/webrtc/opcodes/SelectProtocol.ts +++ b/src/webrtc/opcodes/SelectProtocol.ts @@ -19,22 +19,22 @@ import { VoiceOPCodes, VoicePayload, WebRtcWebSocket, mediaServer, Send } from "@spacebar/webrtc"; export async function onSelectProtocol(this: WebRtcWebSocket, payload: VoicePayload) { - if (!this.webRtcClient) return; + if (!this.webRtcClient) return; - const data = validateSchema("SelectProtocolSchema", payload.d) as SelectProtocolSchema; + const data = validateSchema("SelectProtocolSchema", payload.d) as SelectProtocolSchema; - // UDP protocol not currently supported. Maybe in the future? - if (data.protocol !== "webrtc") return this.close(4000, "only webrtc protocol supported currently"); + // UDP protocol not currently supported. Maybe in the future? + if (data.protocol !== "webrtc") return this.close(4000, "only webrtc protocol supported currently"); - const response = await mediaServer.onOffer(this.webRtcClient, data.sdp!, data.codecs ?? []); + const response = await mediaServer.onOffer(this.webRtcClient, data.sdp!, data.codecs ?? []); - await Send(this, { - op: VoiceOPCodes.SESSION_DESCRIPTION, - d: { - video_codec: response.selectedVideoCodec, - sdp: response.sdp, - media_session_id: this.session_id, - audio_codec: "opus", - }, - }); + await Send(this, { + op: VoiceOPCodes.SESSION_DESCRIPTION, + d: { + video_codec: response.selectedVideoCodec, + sdp: response.sdp, + media_session_id: this.session_id, + audio_codec: "opus", + }, + }); } diff --git a/src/webrtc/opcodes/Speaking.ts b/src/webrtc/opcodes/Speaking.ts index fde63f3..519797a 100644 --- a/src/webrtc/opcodes/Speaking.ts +++ b/src/webrtc/opcodes/Speaking.ts @@ -21,22 +21,22 @@ // {"speaking":1,"delay":5,"ssrc":2805246727} export async function onSpeaking(this: WebRtcWebSocket, data: VoicePayload) { - if (!this.webRtcClient) return; + if (!this.webRtcClient) return; - await Promise.all( - Array.from(mediaServer.getClientsForRtcServer<WebRtcWebSocket>(this.webRtcClient.voiceRoomId)).map((client) => { - if (client.user_id === this.user_id) return Promise.resolve(); + await Promise.all( + Array.from(mediaServer.getClientsForRtcServer<WebRtcWebSocket>(this.webRtcClient.voiceRoomId)).map((client) => { + if (client.user_id === this.user_id) return Promise.resolve(); - const ssrc = client.getOutgoingStreamSSRCsForUser(this.user_id); + const ssrc = client.getOutgoingStreamSSRCsForUser(this.user_id); - return Send(client.websocket, { - op: VoiceOPCodes.SPEAKING, - d: { - user_id: this.user_id, - speaking: data.d.speaking, - ssrc: ssrc.audio_ssrc ?? 0, - }, - }); - }), - ); + return Send(client.websocket, { + op: VoiceOPCodes.SPEAKING, + d: { + user_id: this.user_id, + speaking: data.d.speaking, + ssrc: ssrc.audio_ssrc ?? 0, + }, + }); + }), + ); } diff --git a/src/webrtc/opcodes/Video.ts b/src/webrtc/opcodes/Video.ts index ed8870e..a742619 100644 --- a/src/webrtc/opcodes/Video.ts +++ b/src/webrtc/opcodes/Video.ts @@ -21,190 +21,190 @@ import { validateSchema, VoiceVideoSchema } from "@spacebar/schemas"; export async function onVideo(this: WebRtcWebSocket, payload: VoicePayload) { - if (!this.webRtcClient) return; + if (!this.webRtcClient) return; - const { voiceRoomId } = this.webRtcClient; + const { voiceRoomId } = this.webRtcClient; - const d = validateSchema("VoiceVideoSchema", payload.d) as VoiceVideoSchema; + const d = validateSchema("VoiceVideoSchema", payload.d) as VoiceVideoSchema; - if (this.type === "stream") { - const stream = await Stream.findOne({ - where: { id: voiceRoomId }, - }); + if (this.type === "stream") { + const stream = await Stream.findOne({ + where: { id: voiceRoomId }, + }); - if (!stream) return; + if (!stream) return; - // only the stream owner can publish to a go live stream - if (stream?.owner_id != this.user_id) { - return; - } - } + // only the stream owner can publish to a go live stream + if (stream?.owner_id != this.user_id) { + return; + } + } - const stream = d.streams?.find((element) => element.active); + const stream = d.streams?.find((element) => element.active); - const clientsThatNeedUpdate = new Set<WebRtcClient<WebRtcWebSocket>>(); - const wantsToProduceAudio = d.audio_ssrc !== 0; - const wantsToProduceVideo = d.video_ssrc !== 0 && stream?.active; + const clientsThatNeedUpdate = new Set<WebRtcClient<WebRtcWebSocket>>(); + const wantsToProduceAudio = d.audio_ssrc !== 0; + const wantsToProduceVideo = d.video_ssrc !== 0 && stream?.active; - // this is to handle a really weird case where the client sends audio info before the - // dtls ice connection is completely connected. Wait for connection for 3 seconds - // and if no connection, just ignore this message - if (!this.webRtcClient.webrtcConnected) { - if (wantsToProduceAudio) { - try { - await Promise.race([ - new Promise<void>((resolve, reject) => { - this.webRtcClient?.emitter.once("connected", () => resolve()); - }), - new Promise<void>((resolve, reject) => { - // Reject after 3 seconds if still not connected - setTimeout(() => { - if (this.webRtcClient?.webrtcConnected) resolve(); - else reject(); - }, 3000); - }), - ]); - } catch (e) { - return; // just ignore this message if client didn't connect within 3 seconds - } - } else return; - } + // this is to handle a really weird case where the client sends audio info before the + // dtls ice connection is completely connected. Wait for connection for 3 seconds + // and if no connection, just ignore this message + if (!this.webRtcClient.webrtcConnected) { + if (wantsToProduceAudio) { + try { + await Promise.race([ + new Promise<void>((resolve, reject) => { + this.webRtcClient?.emitter.once("connected", () => resolve()); + }), + new Promise<void>((resolve, reject) => { + // Reject after 3 seconds if still not connected + setTimeout(() => { + if (this.webRtcClient?.webrtcConnected) resolve(); + else reject(); + }, 3000); + }), + ]); + } catch (e) { + return; // just ignore this message if client didn't connect within 3 seconds + } + } else return; + } - await Send(this, { op: VoiceOPCodes.MEDIA_SINK_WANTS, d: { any: 100 } }); + await Send(this, { op: VoiceOPCodes.MEDIA_SINK_WANTS, d: { any: 100 } }); - // first check if we need stop any tracks - if (!wantsToProduceAudio && this.webRtcClient.isProducingAudio()) { - this.webRtcClient.stopPublishingTrack("audio"); - } + // first check if we need stop any tracks + if (!wantsToProduceAudio && this.webRtcClient.isProducingAudio()) { + this.webRtcClient.stopPublishingTrack("audio"); + } - if (!wantsToProduceVideo && this.webRtcClient.isProducingVideo()) { - this.webRtcClient.stopPublishingTrack("video"); - } + if (!wantsToProduceVideo && this.webRtcClient.isProducingVideo()) { + this.webRtcClient.stopPublishingTrack("video"); + } - // check if client has signaled that it will send audio - if (wantsToProduceAudio) { - // check if we are already producing audio, if not, publish a new audio track for it - if (!this.webRtcClient!.isProducingAudio()) { - console.log(`[${this.user_id}] publishing new audio track ssrc:${d.audio_ssrc}`); - await this.webRtcClient.publishTrack("audio", { - audio_ssrc: d.audio_ssrc, - }); - } + // check if client has signaled that it will send audio + if (wantsToProduceAudio) { + // check if we are already producing audio, if not, publish a new audio track for it + if (!this.webRtcClient!.isProducingAudio()) { + console.log(`[${this.user_id}] publishing new audio track ssrc:${d.audio_ssrc}`); + await this.webRtcClient.publishTrack("audio", { + audio_ssrc: d.audio_ssrc, + }); + } - // now check that all clients have subscribed to our audio - for (const client of mediaServer.getClientsForRtcServer<WebRtcWebSocket>(voiceRoomId)) { - if (client.user_id === this.user_id) continue; + // now check that all clients have subscribed to our audio + for (const client of mediaServer.getClientsForRtcServer<WebRtcWebSocket>(voiceRoomId)) { + if (client.user_id === this.user_id) continue; - if (!client.isSubscribedToTrack(this.user_id, "audio")) { - console.log(`[${client.user_id}] subscribing to audio track ssrcs: ${d.audio_ssrc}`); - await client.subscribeToTrack(this.webRtcClient.user_id, "audio"); + if (!client.isSubscribedToTrack(this.user_id, "audio")) { + console.log(`[${client.user_id}] subscribing to audio track ssrcs: ${d.audio_ssrc}`); + await client.subscribeToTrack(this.webRtcClient.user_id, "audio"); - clientsThatNeedUpdate.add(client); - } - } - } - // check if client has signaled that it will send video - if (wantsToProduceVideo) { - this.webRtcClient!.videoStream = { ...stream, type: "video" }; // client sends "screen" on go live but expects "video" on response - // check if we are already publishing video, if not, publish a new video track for it - if (!this.webRtcClient!.isProducingVideo()) { - console.log(`[${this.user_id}] publishing new video track ssrc:${d.video_ssrc}`); - await this.webRtcClient.publishTrack("video", { - video_ssrc: d.video_ssrc, - rtx_ssrc: d.rtx_ssrc, - }); - } + clientsThatNeedUpdate.add(client); + } + } + } + // check if client has signaled that it will send video + if (wantsToProduceVideo) { + this.webRtcClient!.videoStream = { ...stream, type: "video" }; // client sends "screen" on go live but expects "video" on response + // check if we are already publishing video, if not, publish a new video track for it + if (!this.webRtcClient!.isProducingVideo()) { + console.log(`[${this.user_id}] publishing new video track ssrc:${d.video_ssrc}`); + await this.webRtcClient.publishTrack("video", { + video_ssrc: d.video_ssrc, + rtx_ssrc: d.rtx_ssrc, + }); + } - // now check that all clients have subscribed to our video track - for (const client of mediaServer.getClientsForRtcServer<WebRtcWebSocket>(voiceRoomId)) { - if (client.user_id === this.user_id) continue; + // now check that all clients have subscribed to our video track + for (const client of mediaServer.getClientsForRtcServer<WebRtcWebSocket>(voiceRoomId)) { + if (client.user_id === this.user_id) continue; - if (!client.isSubscribedToTrack(this.user_id, "video")) { - console.log(`[${client.user_id}] subscribing to video track ssrc: ${d.video_ssrc}`); - await client.subscribeToTrack(this.webRtcClient.user_id, "video"); + if (!client.isSubscribedToTrack(this.user_id, "video")) { + console.log(`[${client.user_id}] subscribing to video track ssrc: ${d.video_ssrc}`); + await client.subscribeToTrack(this.webRtcClient.user_id, "video"); - clientsThatNeedUpdate.add(client); - } - } - } + clientsThatNeedUpdate.add(client); + } + } + } - await Promise.all( - Array.from(clientsThatNeedUpdate).map((client) => { - const ssrcs = client.getOutgoingStreamSSRCsForUser(this.user_id); + await Promise.all( + Array.from(clientsThatNeedUpdate).map((client) => { + const ssrcs = client.getOutgoingStreamSSRCsForUser(this.user_id); - return Send(client.websocket, { - op: VoiceOPCodes.VIDEO, - d: { - user_id: this.user_id, - // can never send audio ssrc as 0, it will mess up client state for some reason. send server generated ssrc as backup - audio_ssrc: ssrcs.audio_ssrc ?? this.webRtcClient!.getIncomingStreamSSRCs().audio_ssrc, - video_ssrc: ssrcs.video_ssrc ?? 0, - rtx_ssrc: ssrcs.rtx_ssrc ?? 0, - streams: d.streams?.map((x) => ({ - ...x, - ssrc: ssrcs.video_ssrc ?? 0, - rtx_ssrc: ssrcs.rtx_ssrc ?? 0, - type: "video", - })), - } as VoiceVideoSchema, - }); - }), - ); + return Send(client.websocket, { + op: VoiceOPCodes.VIDEO, + d: { + user_id: this.user_id, + // can never send audio ssrc as 0, it will mess up client state for some reason. send server generated ssrc as backup + audio_ssrc: ssrcs.audio_ssrc ?? this.webRtcClient!.getIncomingStreamSSRCs().audio_ssrc, + video_ssrc: ssrcs.video_ssrc ?? 0, + rtx_ssrc: ssrcs.rtx_ssrc ?? 0, + streams: d.streams?.map((x) => ({ + ...x, + ssrc: ssrcs.video_ssrc ?? 0, + rtx_ssrc: ssrcs.rtx_ssrc ?? 0, + type: "video", + })), + } as VoiceVideoSchema, + }); + }), + ); } // check if we are not subscribed to producers in this server, if not, subscribe export async function subscribeToProducers(this: WebRtcWebSocket): Promise<void> { - if (!this.webRtcClient || !this.webRtcClient.webrtcConnected) return; + if (!this.webRtcClient || !this.webRtcClient.webrtcConnected) return; - const clients = mediaServer.getClientsForRtcServer<WebRtcWebSocket>(this.webRtcClient.voiceRoomId); + const clients = mediaServer.getClientsForRtcServer<WebRtcWebSocket>(this.webRtcClient.voiceRoomId); - await Promise.all( - Array.from(clients).map(async (client) => { - let needsUpdate = false; + await Promise.all( + Array.from(clients).map(async (client) => { + let needsUpdate = false; - if (client.user_id === this.user_id) return; // cannot subscribe to self + if (client.user_id === this.user_id) return; // cannot subscribe to self - if (client.isProducingAudio() && !this.webRtcClient!.isSubscribedToTrack(client.user_id, "audio")) { - await this.webRtcClient!.subscribeToTrack(client.user_id, "audio"); - needsUpdate = true; - } + if (client.isProducingAudio() && !this.webRtcClient!.isSubscribedToTrack(client.user_id, "audio")) { + await this.webRtcClient!.subscribeToTrack(client.user_id, "audio"); + needsUpdate = true; + } - if (client.isProducingVideo() && !this.webRtcClient!.isSubscribedToTrack(client.user_id, "video")) { - await this.webRtcClient!.subscribeToTrack(client.user_id, "video"); - needsUpdate = true; - } + if (client.isProducingVideo() && !this.webRtcClient!.isSubscribedToTrack(client.user_id, "video")) { + await this.webRtcClient!.subscribeToTrack(client.user_id, "video"); + needsUpdate = true; + } - if (!needsUpdate) return; + if (!needsUpdate) return; - const ssrcs = this.webRtcClient!.getOutgoingStreamSSRCsForUser(client.user_id); + const ssrcs = this.webRtcClient!.getOutgoingStreamSSRCsForUser(client.user_id); - await Send(this, { - op: VoiceOPCodes.VIDEO, - d: { - user_id: client.user_id, - // can never send audio ssrc as 0, it will mess up client state for some reason. send server generated ssrc as backup - audio_ssrc: ssrcs.audio_ssrc ?? client.getIncomingStreamSSRCs().audio_ssrc, - video_ssrc: ssrcs.video_ssrc ?? 0, - rtx_ssrc: ssrcs.rtx_ssrc ?? 0, - streams: [ - client.videoStream ?? { - type: "video", - rid: "100", - ssrc: ssrcs.video_ssrc ?? 0, - active: client.isProducingVideo(), - quality: 100, - rtx_ssrc: ssrcs.rtx_ssrc ?? 0, - max_bitrate: 2500000, - max_framerate: 20, - max_resolution: { - type: "fixed", - width: 1280, - height: 720, - }, - }, - ], - } as VoiceVideoSchema, - }); - }), - ); + await Send(this, { + op: VoiceOPCodes.VIDEO, + d: { + user_id: client.user_id, + // can never send audio ssrc as 0, it will mess up client state for some reason. send server generated ssrc as backup + audio_ssrc: ssrcs.audio_ssrc ?? client.getIncomingStreamSSRCs().audio_ssrc, + video_ssrc: ssrcs.video_ssrc ?? 0, + rtx_ssrc: ssrcs.rtx_ssrc ?? 0, + streams: [ + client.videoStream ?? { + type: "video", + rid: "100", + ssrc: ssrcs.video_ssrc ?? 0, + active: client.isProducingVideo(), + quality: 100, + rtx_ssrc: ssrcs.rtx_ssrc ?? 0, + max_bitrate: 2500000, + max_framerate: 20, + max_resolution: { + type: "fixed", + width: 1280, + height: 720, + }, + }, + ], + } as VoiceVideoSchema, + }); + }), + ); } diff --git a/src/webrtc/opcodes/index.ts b/src/webrtc/opcodes/index.ts index 2a79b8f..e8eb7b5 100644 --- a/src/webrtc/opcodes/index.ts +++ b/src/webrtc/opcodes/index.ts @@ -27,10 +27,10 @@ export type OPCodeHandler = (this: WebRtcWebSocket, data: VoicePayload) => Promise<void>; export default { - [VoiceOPCodes.HEARTBEAT]: onHeartbeat, - [VoiceOPCodes.IDENTIFY]: onIdentify, - [VoiceOPCodes.VOICE_BACKEND_VERSION]: onBackendVersion, - [VoiceOPCodes.VIDEO]: onVideo, - [VoiceOPCodes.SPEAKING]: onSpeaking, - [VoiceOPCodes.SELECT_PROTOCOL]: onSelectProtocol, + [VoiceOPCodes.HEARTBEAT]: onHeartbeat, + [VoiceOPCodes.IDENTIFY]: onIdentify, + [VoiceOPCodes.VOICE_BACKEND_VERSION]: onBackendVersion, + [VoiceOPCodes.VIDEO]: onVideo, + [VoiceOPCodes.SPEAKING]: onSpeaking, + [VoiceOPCodes.SELECT_PROTOCOL]: onSelectProtocol, } as { [key: number]: OPCodeHandler }; diff --git a/src/webrtc/start.ts b/src/webrtc/start.ts index 9d1cf47..5ad9761 100644 --- a/src/webrtc/start.ts +++ b/src/webrtc/start.ts @@ -27,6 +27,6 @@ const port = Number(process.env.PORT) || 3004; const server = new Server({ - port, + port, }); server.start(); diff --git a/src/webrtc/util/Constants.ts b/src/webrtc/util/Constants.ts index f11c95f..72aec06 100644 --- a/src/webrtc/util/Constants.ts +++ b/src/webrtc/util/Constants.ts @@ -19,22 +19,22 @@ import { Payload } from "@spacebar/gateway"; export enum VoiceOPCodes { - IDENTIFY = 0, - SELECT_PROTOCOL = 1, - READY = 2, - HEARTBEAT = 3, - SESSION_DESCRIPTION = 4, - SPEAKING = 5, - HEARTBEAT_ACK = 6, - RESUME = 7, - HELLO = 8, - RESUMED = 9, - VIDEO = 12, - CLIENT_DISCONNECT = 13, - SESSION_UPDATE = 14, - MEDIA_SINK_WANTS = 15, - VOICE_BACKEND_VERSION = 16, - CHANNEL_OPTIONS_UPDATE = 17, + IDENTIFY = 0, + SELECT_PROTOCOL = 1, + READY = 2, + HEARTBEAT = 3, + SESSION_DESCRIPTION = 4, + SPEAKING = 5, + HEARTBEAT_ACK = 6, + RESUME = 7, + HELLO = 8, + RESUMED = 9, + VIDEO = 12, + CLIENT_DISCONNECT = 13, + SESSION_UPDATE = 14, + MEDIA_SINK_WANTS = 15, + VOICE_BACKEND_VERSION = 16, + CHANNEL_OPTIONS_UPDATE = 17, } export type VoicePayload = Omit<Payload, "op"> & { op: VoiceOPCodes }; diff --git a/src/webrtc/util/MediaServer.ts b/src/webrtc/util/MediaServer.ts index 52ed773..0f64bc0 100644 --- a/src/webrtc/util/MediaServer.ts +++ b/src/webrtc/util/MediaServer.ts @@ -29,41 +29,41 @@ // could not find a way to hide stack trace from base Error object class NoConfiguredLibraryError implements Error { - name: string; - message: string; - stack?: string | undefined; - cause?: unknown; + name: string; + message: string; + stack?: string | undefined; + cause?: unknown; - constructor(message: string) { - this.name = "NoConfiguredLibraryError"; - this.message = message; - } + constructor(message: string) { + this.name = "NoConfiguredLibraryError"; + this.message = message; + } } export const loadWebRtcLibrary = async () => { - try { - //mediaServer = require('medooze-spacebar-wrtc'); - if (!selectedWrtcLibrary) throw new NoConfiguredLibraryError("No library configured in .env"); + try { + //mediaServer = require('medooze-spacebar-wrtc'); + if (!selectedWrtcLibrary) throw new NoConfiguredLibraryError("No library configured in .env"); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - mediaServer = new // @ts-ignore - (await import(selectedWrtcLibrary)).default(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + mediaServer = new // @ts-ignore + (await import(selectedWrtcLibrary)).default(); - console.log(`[WebRTC] ${green(`Succesfully loaded ${selectedWrtcLibrary}`)}`); - return Promise.resolve(); - } catch (error) { - console.log(`[WebRTC] ${red(`Failed to import ${selectedWrtcLibrary}: ${error instanceof NoConfiguredLibraryError ? error.message : ""}`)}`); + console.log(`[WebRTC] ${green(`Succesfully loaded ${selectedWrtcLibrary}`)}`); + return Promise.resolve(); + } catch (error) { + console.log(`[WebRTC] ${red(`Failed to import ${selectedWrtcLibrary}: ${error instanceof NoConfiguredLibraryError ? error.message : ""}`)}`); - return Promise.reject(); - } + return Promise.reject(); + } }; const MAX_INT32BIT = 2 ** 32; let count = 1; export const generateSsrc = () => { - count++; - if (count >= MAX_INT32BIT) count = 1; + count++; + if (count >= MAX_INT32BIT) count = 1; - return count; + return count; }; diff --git a/src/webrtc/util/Send.ts b/src/webrtc/util/Send.ts index cfd0b63..1d8aa61 100644 --- a/src/webrtc/util/Send.ts +++ b/src/webrtc/util/Send.ts @@ -3,24 +3,24 @@ import { WebRtcWebSocket } from "./WebRtcWebSocket"; export function Send(socket: WebRtcWebSocket, data: VoicePayload) { - if (process.env.WRTC_WS_VERBOSE) console.log(`[WebRTC] Outgoing message: ${JSON.stringify(data)}`); + if (process.env.WRTC_WS_VERBOSE) console.log(`[WebRTC] Outgoing message: ${JSON.stringify(data)}`); - let buffer: Buffer | string; + let buffer: Buffer | string; - // TODO: encode circular object - if (socket.encoding === "json") buffer = JSON.stringify(data, JSONReplacer); - else return; + // TODO: encode circular object + if (socket.encoding === "json") buffer = JSON.stringify(data, JSONReplacer); + else return; - return new Promise((res, rej) => { - if (socket.readyState !== 1) { - // return rej("socket not open"); - socket.close(); - return; - } + return new Promise((res, rej) => { + if (socket.readyState !== 1) { + // return rej("socket not open"); + socket.close(); + return; + } - socket.send(buffer, (err) => { - if (err) return rej(err); - return res(null); - }); - }); + socket.send(buffer, (err) => { + if (err) return rej(err); + return res(null); + }); + }); } diff --git a/src/webrtc/util/WebRtcWebSocket.ts b/src/webrtc/util/WebRtcWebSocket.ts index b97c169..e36b4f1 100644 --- a/src/webrtc/util/WebRtcWebSocket.ts +++ b/src/webrtc/util/WebRtcWebSocket.ts @@ -2,6 +2,6 @@ import type { WebRtcClient } from "@spacebarchat/spacebar-webrtc-types"; export interface WebRtcWebSocket extends WebSocket { - type: "guild-voice" | "dm-voice" | "stream"; - webRtcClient?: WebRtcClient<WebRtcWebSocket>; + type: "guild-voice" | "dm-voice" | "stream"; + webRtcClient?: WebRtcClient<WebRtcWebSocket>; } diff --git a/tsconfig.json b/tsconfig.json index 374aedf..6070877 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,113 +1,113 @@ { - "include": ["./src"], - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ + "include": ["./src"], + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */, - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Projects */ + "incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */, + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - "lib": ["ESNext"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, - // "jsx": "preserve", /* Specify what JSX code is generated. */ - "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, - "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Language and Environment */ + "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": ["ESNext"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + // "jsx": "preserve", /* Specify what JSX code is generated. */ + "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, + "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "NodeNext" /* Specify what module code is generated. */, - // "rootDir": "./src", /* Specify the root folder within your source files. */ - "moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */, - // "baseUrl": "./src/", //deprecated - "paths": { - //"*": ["./src/*"], - "@spacebar/api*": ["./src/api"], - "@spacebar/gateway*": ["./src/gateway"], - "@spacebar/cdn*": ["./src/cdn"], - "@spacebar/util*": ["./src/util"], - "@spacebar/webrtc*": ["./src/webrtc"], - "@spacebar/schemas*": ["./src/schemas"], - "lambert-server*": ["./src/util/util/lambert-server"] - } /* Specify a set of entries that re-map imports to additional lookup locations. */, - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - "types": ["node"] /* Specify type package names to be included without being referenced in a source file. */, - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - "resolveJsonModule": true /* Enable importing .json files. */, - // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "module": "NodeNext" /* Specify what module code is generated. */, + // "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./src/", //deprecated + "paths": { + //"*": ["./src/*"], + "@spacebar/api*": ["./src/api"], + "@spacebar/gateway*": ["./src/gateway"], + "@spacebar/cdn*": ["./src/cdn"], + "@spacebar/util*": ["./src/util"], + "@spacebar/webrtc*": ["./src/webrtc"], + "@spacebar/schemas*": ["./src/schemas"], + "lambert-server*": ["./src/util/util/lambert-server"] + } /* Specify a set of entries that re-map imports to additional lookup locations. */, + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + "types": ["node"] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + "resolveJsonModule": true /* Enable importing .json files. */, + // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, - "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, - "declarationMap": true /* Create sourcemaps for d.ts files. */, - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - "sourceMap": true /* Create source map files for emitted JavaScript files. */, - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist/" /* Specify an output folder for all emitted files. */, - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - "importHelpers": true /* Allow importing helper functions from tslib once per project, instead of including them per-file. */, - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - "noEmitHelpers": true /* Disable generating custom helper functions like '__extends' in compiled output. */, - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Emit */ + "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, + "declarationMap": true /* Create sourcemaps for d.ts files. */, + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true /* Create source map files for emitted JavaScript files. */, + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist/" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + "importHelpers": true /* Allow importing helper functions from tslib once per project, instead of including them per-file. */, + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + "noEmitHelpers": true /* Disable generating custom helper functions like '__extends' in compiled output. */, + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, - "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */, - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */, - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - "alwaysStrict": true /* Ensure 'use strict' is always emitted. */, - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, + "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */, + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */, + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + "alwaysStrict": true /* Ensure 'use strict' is always emitted. */, + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } }