diff options
author | Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> | 2021-08-12 20:33:02 +0200 |
---|---|---|
committer | Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> | 2021-08-12 20:33:02 +0200 |
commit | c72661a98782c1316038382a4989b3a05bdab731 (patch) | |
tree | 83d4ba2b54a3cb1b761703993a3b43bd2e5db797 /util | |
parent | Merge branch 'rtc' (diff) | |
download | server-c72661a98782c1316038382a4989b3a05bdab731.tar.xz |
:sparkles: util
Diffstat (limited to 'util')
47 files changed, 5277 insertions, 0 deletions
diff --git a/util/.gitignore b/util/.gitignore new file mode 100644 index 00000000..d7fc3f74 --- /dev/null +++ b/util/.gitignore @@ -0,0 +1,107 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port +.DS_Store + +# Compiled TypeScript code +dist/ \ No newline at end of file diff --git a/util/.npmignore b/util/.npmignore new file mode 100644 index 00000000..05a9d0cf --- /dev/null +++ b/util/.npmignore @@ -0,0 +1 @@ +!dist/ \ No newline at end of file diff --git a/util/.prettierrc b/util/.prettierrc new file mode 100644 index 00000000..d569c548 --- /dev/null +++ b/util/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 4, + "useTabs": true, + "printWidth": 120 +} diff --git a/util/LICENSE b/util/LICENSE new file mode 100644 index 00000000..f19bf520 --- /dev/null +++ b/util/LICENSE @@ -0,0 +1,14 @@ +Copyright (C) 2021 Fosscord and contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see <https://www.gnu.org/licenses/>. \ No newline at end of file diff --git a/util/README.md b/util/README.md new file mode 100644 index 00000000..9c983acd --- /dev/null +++ b/util/README.md @@ -0,0 +1,29 @@ +<p align="center"> + <img width="100" src="https://raw.githubusercontent.com/fosscord/fosscord/master/assets/logo_big_transparent.png" /> +</p> +<h1 align="center">Fosscord server util</h1> + +<p> + <a href="https://discord.gg/ZrnGQP6p3d"> + <img src="https://img.shields.io/discord/806142446094385153?color=7489d5&logo=discord&logoColor=ffffff" /> + </a> + <img src="https://img.shields.io/static/v1?label=Status&message=Development&color=blue"> + <a title="Crowdin" target="_blank" href="https://translate.fosscord.com/"><img src="https://badges.crowdin.net/fosscord/localized.svg"></a> + <a href="https://opencollective.com/fosscord"> + <img src="https://opencollective.com/fosscord/tiers/badge.svg"> + </a> +</p> + +## [About](https://fosscord.com) + +Fosscord is a free open source selfhostable chat, voice and video discord-compatible platform. + +Fosscord server util contains all necessary logic that is shared between the [api](https://github.com/fosscord/fosscord-api), [gateway](https://github.com/fosscord/fosscord-gateway) and [cdn](https://github.com/fosscord/fosscord-cdn). + +It contains all mongoose database models and utility functions. + +## Installation + +```bash +npm install @fosscord/server-util +``` diff --git a/util/package-lock.json b/util/package-lock.json new file mode 100644 index 00000000..4977468d --- /dev/null +++ b/util/package-lock.json @@ -0,0 +1,1268 @@ +{ + "name": "@fosscord/server-util", + "version": "1.3.52", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@fosscord/server-util", + "version": "1.3.52", + "license": "GPLV3", + "dependencies": { + "@types/jsonwebtoken": "^8.5.0", + "@types/mongoose-autopopulate": "^0.10.1", + "@types/mongoose-lean-virtuals": "^0.5.1", + "@types/node": "^14.14.25", + "ajv": "^8.5.0", + "amqplib": "^0.8.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "jsonwebtoken": "^8.5.1", + "missing-native-js-functions": "^1.2.2", + "mongodb": "^3.6.9", + "mongoose": "^5.12.3", + "mongoose-autopopulate": "^0.12.3", + "typescript": "^4.1.3" + }, + "devDependencies": { + "@types/amqplib": "^0.8.1" + } + }, + "node_modules/@types/amqplib": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.8.1.tgz", + "integrity": "sha512-8dCjF+dHZ8Y6JOoHD1BMnxP0quAncvZq4wA/lS072NjX9vIzVRSMcmfKy2Os8ZQ8VWWp74MD09GMbVbKS6/Fxw==", + "dev": true, + "dependencies": { + "@types/bluebird": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bluebird": { + "version": "3.5.36", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", + "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", + "dev": true + }, + "node_modules/@types/bson": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", + "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mongodb": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.6.tgz", + "integrity": "sha512-ghYevKiSh/TGk2MAwSRZP7T1ilR9Pw8Fa7pT9GGVGZPUsWKdZjZ4G6LG3MqK2iXKdNba994F8W9ikA+qx2Eo3A==", + "dependencies": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mongoose": { + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.10.4.tgz", + "integrity": "sha512-U7fNDcTcdaSGzQ3+mlSBeebiYr6eaacJi330LTLOEh8Sm6mXfuec70ag/UXkL+alFm7pfAjFqfc7jEaJEJvAHQ==", + "dependencies": { + "@types/mongodb": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mongoose-autopopulate": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@types/mongoose-autopopulate/-/mongoose-autopopulate-0.10.1.tgz", + "integrity": "sha512-L67MAIE3WEoTtt7a7/spRYk+76lgp67FAP6I38Y9NcC1kQuzwqnukTaJzodfb8180wxHZM4qt68u6x6ptuDRaQ==", + "dependencies": { + "@types/mongoose": "*" + } + }, + "node_modules/@types/mongoose-lean-virtuals": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/mongoose-lean-virtuals/-/mongoose-lean-virtuals-0.5.1.tgz", + "integrity": "sha512-bNk+QLjP5VZU4EsJag4xQsjLAa8CEm/SKZDyiC2kM208wIrGum6daD7j45Oqs50bWNGfqZYRuEhh8xZ17D7aEw==", + "dependencies": { + "@types/mongoose": "*" + } + }, + "node_modules/@types/node": { + "version": "14.14.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz", + "integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==" + }, + "node_modules/ajv": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", + "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/amqplib": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.8.0.tgz", + "integrity": "sha512-icU+a4kkq4Y1PS4NNi+YPDMwdlbFcZ1EZTQT2nigW3fvOb6AOgUQ9+Mk4ue0Zu5cBg/XpDzB40oH10ysrk2dmA==", + "dependencies": { + "bitsyntax": "~0.1.0", + "bluebird": "^3.7.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "safe-buffer": "~5.2.1", + "url-parse": "~1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/amqplib/node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/amqplib/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/amqplib/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/amqplib/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/bitsyntax": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", + "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "debug": "~2.6.9", + "safe-buffer": "~5.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/bitsyntax/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==" + }, + "node_modules/bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "node_modules/bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==", + "engines": { + "node": ">=0.6.19" + } + }, + "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": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "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==" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dependencies": { + "safe-buffer": "^5.0.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==", + "engines": { + "node": ">=6" + } + }, + "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==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "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==" + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "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": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kareem": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", + "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/missing-native-js-functions": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.2.tgz", + "integrity": "sha512-kNdwKWXh1hM8RdNqW2BIHsqD6fYN9RV27M+0uQF1pGF1yLKVc+xIv1VB8WEN1HxQ22N8Rj9sdEezOX2yBpsMZA==" + }, + "node_modules/mongodb": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.9.tgz", + "integrity": "sha512-1nSCKgSunzn/CXwgOWgbPHUWOO5OfERcuOWISmqd610jn0s8BU9K4879iJVabqgpPPbA6hO7rG48eq+fGED3Mg==", + "dependencies": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.0.3", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "saslprep": "^1.0.0" + }, + "peerDependenciesMeta": { + "aws4": { + "optional": true + }, + "bson-ext": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "mongodb-extjson": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongoose": { + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.3.tgz", + "integrity": "sha512-frsSR9yeldaRpSUeTegXCSB0Tu5UGq8sHuHBuEV31Jk3COyxlKFQPL7UsdMhxPUCmk74FpOYSmNwxhWBEqgzQg==", + "dependencies": { + "@types/mongodb": "^3.5.27", + "bson": "^1.1.4", + "kareem": "2.3.2", + "mongodb": "3.6.5", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.8.3", + "mquery": "3.2.5", + "ms": "2.1.2", + "regexp-clone": "1.0.0", + "safe-buffer": "5.2.1", + "sift": "7.0.1", + "sliced": "1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mongoose-autopopulate": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/mongoose-autopopulate/-/mongoose-autopopulate-0.12.3.tgz", + "integrity": "sha512-yNmYsfi6OpS/GQ+48mkB0KQ199ExHmmPrt3wt3fyxPHPMtEBGts7yq3wBQR6VgKCPOQaKvCI1URbJCPOtrPeLw==" + }, + "node_modules/mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.5.tgz", + "integrity": "sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg==", + "dependencies": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "saslprep": "^1.0.0" + } + }, + "node_modules/mpath": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", + "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", + "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", + "dependencies": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "regexp-clone": "^1.0.0", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/mquery/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==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/optional-require": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", + "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/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==" + }, + "node_modules/regexp-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", + "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" + }, + "node_modules/require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "dependencies": { + "resolve-from": "^2.0.0", + "semver": "^5.1.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==", + "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": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "node_modules/resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "engines": { + "node": ">=0.10.0" + } + }, + "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==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sift": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", + "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" + }, + "node_modules/sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/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==" + }, + "node_modules/typescript": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", + "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", + "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + } + }, + "dependencies": { + "@types/amqplib": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.8.1.tgz", + "integrity": "sha512-8dCjF+dHZ8Y6JOoHD1BMnxP0quAncvZq4wA/lS072NjX9vIzVRSMcmfKy2Os8ZQ8VWWp74MD09GMbVbKS6/Fxw==", + "dev": true, + "requires": { + "@types/bluebird": "*", + "@types/node": "*" + } + }, + "@types/bluebird": { + "version": "3.5.36", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", + "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", + "dev": true + }, + "@types/bson": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", + "requires": { + "@types/node": "*" + } + }, + "@types/jsonwebtoken": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", + "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "requires": { + "@types/node": "*" + } + }, + "@types/mongodb": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.6.tgz", + "integrity": "sha512-ghYevKiSh/TGk2MAwSRZP7T1ilR9Pw8Fa7pT9GGVGZPUsWKdZjZ4G6LG3MqK2iXKdNba994F8W9ikA+qx2Eo3A==", + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "@types/mongoose": { + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.10.4.tgz", + "integrity": "sha512-U7fNDcTcdaSGzQ3+mlSBeebiYr6eaacJi330LTLOEh8Sm6mXfuec70ag/UXkL+alFm7pfAjFqfc7jEaJEJvAHQ==", + "requires": { + "@types/mongodb": "*", + "@types/node": "*" + } + }, + "@types/mongoose-autopopulate": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@types/mongoose-autopopulate/-/mongoose-autopopulate-0.10.1.tgz", + "integrity": "sha512-L67MAIE3WEoTtt7a7/spRYk+76lgp67FAP6I38Y9NcC1kQuzwqnukTaJzodfb8180wxHZM4qt68u6x6ptuDRaQ==", + "requires": { + "@types/mongoose": "*" + } + }, + "@types/mongoose-lean-virtuals": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/mongoose-lean-virtuals/-/mongoose-lean-virtuals-0.5.1.tgz", + "integrity": "sha512-bNk+QLjP5VZU4EsJag4xQsjLAa8CEm/SKZDyiC2kM208wIrGum6daD7j45Oqs50bWNGfqZYRuEhh8xZ17D7aEw==", + "requires": { + "@types/mongoose": "*" + } + }, + "@types/node": { + "version": "14.14.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz", + "integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==" + }, + "ajv": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", + "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "amqplib": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.8.0.tgz", + "integrity": "sha512-icU+a4kkq4Y1PS4NNi+YPDMwdlbFcZ1EZTQT2nigW3fvOb6AOgUQ9+Mk4ue0Zu5cBg/XpDzB40oH10ysrk2dmA==", + "requires": { + "bitsyntax": "~0.1.0", + "bluebird": "^3.7.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "safe-buffer": "~5.2.1", + "url-parse": "~1.5.1" + }, + "dependencies": { + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "bitsyntax": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", + "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", + "requires": { + "buffer-more-ints": "~1.0.0", + "debug": "~2.6.9", + "safe-buffer": "~5.1.2" + }, + "dependencies": { + "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==" + } + } + }, + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + }, + "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": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "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==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" + }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "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==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" + }, + "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==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "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==" + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "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": "^5.6.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "kareem": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", + "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "missing-native-js-functions": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.2.tgz", + "integrity": "sha512-kNdwKWXh1hM8RdNqW2BIHsqD6fYN9RV27M+0uQF1pGF1yLKVc+xIv1VB8WEN1HxQ22N8Rj9sdEezOX2yBpsMZA==" + }, + "mongodb": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.9.tgz", + "integrity": "sha512-1nSCKgSunzn/CXwgOWgbPHUWOO5OfERcuOWISmqd610jn0s8BU9K4879iJVabqgpPPbA6hO7rG48eq+fGED3Mg==", + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.0.3", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "mongoose": { + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.3.tgz", + "integrity": "sha512-frsSR9yeldaRpSUeTegXCSB0Tu5UGq8sHuHBuEV31Jk3COyxlKFQPL7UsdMhxPUCmk74FpOYSmNwxhWBEqgzQg==", + "requires": { + "@types/mongodb": "^3.5.27", + "bson": "^1.1.4", + "kareem": "2.3.2", + "mongodb": "3.6.5", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.8.3", + "mquery": "3.2.5", + "ms": "2.1.2", + "regexp-clone": "1.0.0", + "safe-buffer": "5.2.1", + "sift": "7.0.1", + "sliced": "1.0.1" + }, + "dependencies": { + "mongodb": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.5.tgz", + "integrity": "sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg==", + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + } + } + }, + "mongoose-autopopulate": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/mongoose-autopopulate/-/mongoose-autopopulate-0.12.3.tgz", + "integrity": "sha512-yNmYsfi6OpS/GQ+48mkB0KQ199ExHmmPrt3wt3fyxPHPMtEBGts7yq3wBQR6VgKCPOQaKvCI1URbJCPOtrPeLw==" + }, + "mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + }, + "mpath": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", + "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" + }, + "mquery": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", + "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "regexp-clone": "^1.0.0", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "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==" + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "optional-require": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", + "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "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==" + } + } + }, + "regexp-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", + "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" + }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "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==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "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==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "sift": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", + "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" + }, + "sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "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==" + } + } + }, + "typescript": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", + "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", + "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + } + } +} diff --git a/util/package.json b/util/package.json new file mode 100644 index 00000000..0478fe69 --- /dev/null +++ b/util/package.json @@ -0,0 +1,48 @@ +{ + "name": "@fosscord/server-util", + "version": "1.3.52", + "description": "Utility functions for the all server repositories", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc -b .", + "prepublish": "npm run build" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/fosscord/fosscord-server-util.git" + }, + "keywords": [ + "discord", + "fosscord", + "fosscord-server-util", + "discord open source", + "discord-open-source" + ], + "author": "Fosscord", + "license": "GPLV3", + "bugs": { + "url": "https://github.com/fosscord/fosscord-server-util/issues" + }, + "homepage": "https://docs.fosscord.com/", + "dependencies": { + "@types/jsonwebtoken": "^8.5.0", + "@types/mongoose-autopopulate": "^0.10.1", + "@types/mongoose-lean-virtuals": "^0.5.1", + "@types/node": "^14.14.25", + "ajv": "^8.5.0", + "amqplib": "^0.8.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "jsonwebtoken": "^8.5.1", + "missing-native-js-functions": "^1.2.2", + "mongodb": "^3.6.9", + "mongoose": "^5.12.3", + "mongoose-autopopulate": "^0.12.3", + "typescript": "^4.1.3" + }, + "devDependencies": { + "@types/amqplib": "^0.8.1" + } +} diff --git a/util/src/index.ts b/util/src/index.ts new file mode 100644 index 00000000..3565fb6b --- /dev/null +++ b/util/src/index.ts @@ -0,0 +1,10 @@ +export * from "./util/checkToken"; + +export * as Constants from "./util/Constants"; +export * from "./models/index"; +export * from "./util/index"; + +import Config from "./util/Config"; +import db, { MongooseCache, toObject } from "./util/Database"; + +export { Config, db, MongooseCache, toObject }; diff --git a/util/src/models/Activity.ts b/util/src/models/Activity.ts new file mode 100644 index 00000000..17abd1ca --- /dev/null +++ b/util/src/models/Activity.ts @@ -0,0 +1,132 @@ +import { User } from ".."; +import { ClientStatus, Status } from "./Status"; +import { Schema, model, Types, Document } from "mongoose"; +import toBigInt from "../util/toBigInt"; + +export interface Presence { + user: User; + guild_id?: string; + status: Status; + activities: Activity[]; + client_status: ClientStatus; +} + +export interface Activity { + name: string; + type: ActivityType; + url?: string; + created_at?: Date; + timestamps?: { + start?: number; + end?: number; + }[]; + application_id?: string; + details?: string; + state?: string; + emoji?: { + name: string; + id?: string; + amimated?: 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?: bigint; +} + +export const ActivitySchema = { + name: { type: String, required: true }, + type: { type: Number, required: true }, + url: String, + created_at: Date, + timestamps: [ + { + start: Number, + end: Number, + }, + ], + application_id: String, + details: String, + state: String, + emoji: { + name: String, + id: String, + amimated: 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: { type: String, get: toBigInt }, +}; + +export const ActivityBodySchema = { + 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, + $amimated: 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: BigInt, +}; + +export enum ActivityType { + GAME = 0, + STREAMING = 1, + LISTENING = 2, + CUSTOM = 4, + COMPETING = 5, +} diff --git a/util/src/models/Application.ts b/util/src/models/Application.ts new file mode 100644 index 00000000..fae6e8db --- /dev/null +++ b/util/src/models/Application.ts @@ -0,0 +1,67 @@ +import { Team } from "./Team"; + +export interface Application { + id: string; + name: string; + icon: string | null; + description: string; + rpc_origins: string[] | null; + bot_public: boolean; + bot_require_code_grant: boolean; + terms_of_service_url: string | null; + privacy_policy_url: string | null; + owner_id: string; + summary: string | null; + verify_key: string; + team: Team | null; + guild_id: string; // if this application is a game sold on Discord, this field will be the guild to which it has been linked + primary_sku_id: string | null; // if this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists + slug: string | null; // if this application is a game sold on Discord, this field will be the URL slug that links to the store page + cover_image: string | null; // the application's default rich presence invite cover image hash + flags: number; // the application's public flags +} + +export interface ApplicationCommand { + 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[]; +} + +export interface ApplicationCommandOptionChoice { + 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, +} + +export interface ApplicationCommandInteractionData { + id: string; + name: string; + options?: ApplicationCommandInteractionDataOption[]; +} + +export interface ApplicationCommandInteractionDataOption { + name: string; + value?: any; + options?: ApplicationCommandInteractionDataOption[]; +} diff --git a/util/src/models/AuditLog.ts b/util/src/models/AuditLog.ts new file mode 100644 index 00000000..02b2c444 --- /dev/null +++ b/util/src/models/AuditLog.ts @@ -0,0 +1,220 @@ +import { Schema, Document, Types } from "mongoose"; +import db from "../util/Database"; +import { ChannelPermissionOverwrite } from "./Channel"; +import { PublicUser } from "./User"; + +export interface AuditLogResponse { + webhooks: []; // TODO: + users: PublicUser[]; + audit_log_entries: AuditLogEntries[]; + integrations: []; // TODO: +} + +export interface AuditLogEntries { + target_id?: string; + user_id: string; + id: string; + action_type: AuditLogEvents; + options?: { + delete_member_days?: string; + members_removed?: string; + channel_id?: string; + messaged_id?: string; + count?: string; + id?: string; + type?: string; + role_name?: string; + }; + changes: AuditLogChange[]; + reason?: string; +} + +export interface AuditLogChange { + 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?: {}[]; + $remove?: {}[]; + 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; +} + +export interface AuditLogEntriesDocument extends Document, AuditLogEntries { + id: string; +} + +export const AuditLogChanges = { + 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: [{}], + $remove: [{}], + prune_delete_days: Number, + widget_enabled: Boolean, + widget_channel_id: String, + system_channel_id: String, + position: Number, + topic: String, + bitrate: Number, + permission_overwrites: [{}], + 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, +}; + +export const AuditLogSchema = new Schema({ + target_id: String, + user_id: { type: String, required: true }, + id: { type: String, required: true }, + action_type: { type: Number, required: true }, + options: { + delete_member_days: String, + members_removed: String, + channel_id: String, + messaged_id: String, + count: String, + id: String, + type: { type: Number }, + role_name: String, + }, + changes: [ + { + new_value: AuditLogChanges, + old_value: AuditLogChanges, + key: String, + }, + ], + reason: String, +}); + +// @ts-ignore +export const AuditLogModel = db.model<AuditLogEntries>("AuditLog", AuditLogSchema, "auditlogs"); + +export enum AuditLogEvents { + GUILD_UPDATE = 1, + CHANNEL_CREATE = 10, + CHANNEL_UPDATE = 11, + CHANNEL_DELETE = 12, + CHANNEL_OVERWRITE_CREATE = 13, + CHANNEL_OVERWRITE_UPDATE = 14, + CHANNEL_OVERWRITE_DELETE = 15, + MEMBER_KICK = 20, + MEMBER_PRUNE = 21, + MEMBER_BAN_ADD = 22, + MEMBER_BAN_REMOVE = 23, + MEMBER_UPDATE = 24, + MEMBER_ROLE_UPDATE = 25, + MEMBER_MOVE = 26, + MEMBER_DISCONNECT = 27, + BOT_ADD = 28, + ROLE_CREATE = 30, + ROLE_UPDATE = 31, + ROLE_DELETE = 32, + INVITE_CREATE = 40, + INVITE_UPDATE = 41, + INVITE_DELETE = 42, + WEBHOOK_CREATE = 50, + WEBHOOK_UPDATE = 51, + WEBHOOK_DELETE = 52, + EMOJI_CREATE = 60, + EMOJI_UPDATE = 61, + EMOJI_DELETE = 62, + MESSAGE_DELETE = 72, + MESSAGE_BULK_DELETE = 73, + MESSAGE_PIN = 74, + MESSAGE_UNPIN = 75, + INTEGRATION_CREATE = 80, + INTEGRATION_UPDATE = 81, + INTEGRATION_DELETE = 82, +} diff --git a/util/src/models/Ban.ts b/util/src/models/Ban.ts new file mode 100644 index 00000000..f09950ee --- /dev/null +++ b/util/src/models/Ban.ts @@ -0,0 +1,32 @@ +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; +import { PublicUserProjection, UserModel } from "./User"; + +export interface Ban extends Document { + user_id: string; + guild_id: string; + executor_id: string; + ip: string; + reason?: string; +} + +export const BanSchema = new Schema({ + user_id: { type: String, required: true }, + guild_id: { type: String, required: true }, + executor_id: { type: String, required: true }, + reason: String, + ip: String, // ? Should we store this in here, or in the UserModel? +}); + +BanSchema.virtual("user", { + ref: UserModel, + localField: "user_id", + foreignField: "id", + justOne: true, + autopopulate: { select: PublicUserProjection }, +}); + +BanSchema.set("removeResponse", ["user_id"]); + +// @ts-ignore +export const BanModel = db.model<Ban>("Ban", BanSchema, "bans"); diff --git a/util/src/models/Channel.ts b/util/src/models/Channel.ts new file mode 100644 index 00000000..1dd05896 --- /dev/null +++ b/util/src/models/Channel.ts @@ -0,0 +1,109 @@ +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; +import toBigInt from "../util/toBigInt"; +import { PublicUserProjection, UserModel } from "./User"; + +// @ts-ignore +export interface AnyChannel extends Channel, DMChannel, TextChannel, VoiceChannel { + recipient_ids: null | string[]; +} + +export interface ChannelDocument extends Document, AnyChannel { + id: string; +} + +export const ChannelSchema = new Schema({ + id: String, + created_at: { type: Schema.Types.Date, required: true }, + name: String, // can't be required for dm channels + type: { type: Number, required: true }, + guild_id: String, + owner_id: String, + parent_id: String, + recipient_ids: [String], + position: Number, + last_message_id: String, + last_pin_timestamp: Date, + nsfw: Boolean, + rate_limit_per_user: Number, + topic: String, + permission_overwrites: [ + { + allow: { type: String, get: toBigInt }, + deny: { type: String, get: toBigInt }, + id: String, + type: { type: Number }, + }, + ], +}); + +ChannelSchema.virtual("recipients", { + ref: UserModel, + localField: "recipient_ids", + foreignField: "id", + justOne: false, + autopopulate: { select: PublicUserProjection }, +}); + +ChannelSchema.set("removeResponse", ["recipient_ids"]); + +// @ts-ignore +export const ChannelModel = db.model<ChannelDocument>("Channel", ChannelSchema, "channels"); + +export interface Channel { + id: string; + created_at: Date; + name: string; + type: number; +} + +export interface TextBasedChannel { + last_message_id?: string; + last_pin_timestamp?: number; +} + +export interface GuildChannel extends Channel { + guild_id: string; + position: number; + parent_id?: string; + permission_overwrites: ChannelPermissionOverwrite[]; +} + +export interface ChannelPermissionOverwrite { + allow: bigint; // for bitfields we use bigints + deny: bigint; // for bitfields we use bigints + id: string; + type: ChannelPermissionOverwriteType; +} + +export enum ChannelPermissionOverwriteType { + role = 0, + member = 1, +} + +export interface VoiceChannel extends GuildChannel { + video_quality_mode?: number; + bitrate?: number; + user_limit?: number; +} + +export interface TextChannel extends GuildChannel, TextBasedChannel { + nsfw: boolean; + rate_limit_per_user: number; + topic?: string; +} +// @ts-ignore +export interface DMChannel extends Channel, TextBasedChannel { + owner_id: string; + recipient_ids: string[]; +} + +export enum ChannelType { + GUILD_TEXT = 0, // a text channel within a server + DM = 1, // a direct message between users + GUILD_VOICE = 2, // a voice channel within a server + GROUP_DM = 3, // a direct message between multiple users + GUILD_CATEGORY = 4, // an organizational category that contains up to 50 channels + GUILD_NEWS = 5, // a channel that users can follow and crosspost into their own server + GUILD_STORE = 6, // a channel in which game developers can sell their game on Discord +} diff --git a/util/src/models/Emoji.ts b/util/src/models/Emoji.ts new file mode 100644 index 00000000..3e5cad53 --- /dev/null +++ b/util/src/models/Emoji.ts @@ -0,0 +1,29 @@ +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; + +export interface Emoji extends Document { + id: string; + animated: boolean; + available: boolean; + guild_id: string; + managed: boolean; + name: string; + require_colons: boolean; + url: string; + roles: string[]; // roles this emoji is whitelisted to (new discord feature?) +} + +export const EmojiSchema = new Schema({ + id: { type: String, required: true }, + animated: Boolean, + available: Boolean, + guild_id: String, + managed: Boolean, + name: String, + require_colons: Boolean, + url: String, + roles: [String], +}); + +// @ts-ignore +export const EmojiModel = db.model<Emoji>("Emoji", EmojiSchema, "emojis"); diff --git a/util/src/models/Event.ts b/util/src/models/Event.ts new file mode 100644 index 00000000..1564107d --- /dev/null +++ b/util/src/models/Event.ts @@ -0,0 +1,540 @@ +import { ConnectedAccount, PublicUser, Relationship, User, UserSettings } from "./User"; +import { DMChannel, Channel } from "./Channel"; +import { Guild } from "./Guild"; +import { Member, PublicMember, UserGuildSettings } from "./Member"; +import { Emoji } from "./Emoji"; +import { Presence } from "./Activity"; +import { Role } from "./Role"; +import { Invite } from "./Invite"; +import { Message, PartialEmoji } from "./Message"; +import { VoiceState } from "./VoiceState"; +import { ApplicationCommand } from "./Application"; +import { Interaction } from "./Interaction"; +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; + +export interface Event { + guild_id?: string; + user_id?: string; + channel_id?: string; + created_at?: Date; + event: EVENT; + data?: any; +} + +export interface EventDocument extends Event, Document {} + +export const EventSchema = new Schema({ + guild_id: String, + user_id: String, + channel_id: String, + created_at: { type: Date, required: true }, + event: { type: String, required: true }, + data: Object, +}); + +// @ts-ignore +export const EventModel = db.model<EventDocument>("Event", EventSchema, "events"); + +// ! Custom Events that shouldn't get sent to the client but processed by the server + +export interface InvalidatedEvent extends Event { + event: "INVALIDATED"; +} + +// ! END Custom Events that shouldn't get sent to the client but processed by the server + +export interface ReadyEventData { + v: number; + user: PublicUser & { + mobile: boolean; + desktop: boolean; + email: string | null; + flags: bigint; + mfa_enabled: boolean; + nsfw_allowed: boolean; + phone: string | null; + premium: boolean; + premium_type: number; + verified: boolean; + bot: boolean; + }; + private_channels: DMChannel[]; // this will be empty for bots + session_id: string; // resuming + guilds: Guild[]; + 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?: []; // ? what is this? this is new + shard?: [number, number]; + user_settings?: UserSettings; + relationships?: Relationship[]; // TODO + read_state: { + entries: []; // TODO + partial: boolean; + version: number; + }; + user_guild_settings?: { + entries: UserGuildSettings[]; + version: number; + partial: boolean; + }; + application?: { + id: string; + flags: bigint; + }; + merged_members?: Omit<Member, "settings" | "user">[][]; + // probably all users who the user is in contact with + users?: { + avatar: string | null; + discriminator: string; + id: string; + username: string; + bot: boolean; + public_flags: bigint; + }[]; +} + +export interface ReadyEvent extends Event { + event: "READY"; + data: ReadyEventData; +} + +export interface ChannelCreateEvent extends Event { + event: "CHANNEL_CREATE"; + data: Channel; +} + +export interface ChannelUpdateEvent extends Event { + event: "CHANNEL_UPDATE"; + data: Channel; +} + +export interface ChannelDeleteEvent extends Event { + event: "CHANNEL_DELETE"; + data: Channel; +} + +export interface ChannelPinsUpdateEvent extends Event { + event: "CHANNEL_PINS_UPDATE"; + data: { + guild_id?: string; + channel_id: string; + last_pin_timestamp?: number; + }; +} + +export interface GuildCreateEvent extends Event { + event: "GUILD_CREATE"; + data: Guild; +} + +export interface GuildUpdateEvent extends Event { + event: "GUILD_UPDATE"; + data: Guild; +} + +export interface GuildDeleteEvent extends Event { + event: "GUILD_DELETE"; + data: { + id: string; + unavailable?: boolean; + }; +} + +export interface GuildBanAddEvent extends Event { + event: "GUILD_BAN_ADD"; + data: { + guild_id: string; + user: User; + }; +} + +export interface GuildBanRemoveEvent extends Event { + event: "GUILD_BAN_REMOVE"; + data: { + guild_id: string; + user: User; + }; +} + +export interface GuildEmojiUpdateEvent extends Event { + event: "GUILD_EMOJI_UPDATE"; + data: { + guild_id: string; + emojis: Emoji[]; + }; +} + +export interface GuildIntegrationUpdateEvent extends Event { + event: "GUILD_INTEGRATIONS_UPDATE"; + data: { + guild_id: string; + }; +} + +export interface GuildMemberAddEvent extends Event { + event: "GUILD_MEMBER_ADD"; + data: PublicMember & { + guild_id: string; + }; +} + +export interface GuildMemberRemoveEvent extends Event { + 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; + }; +} + +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; + }; +} + +export interface GuildRoleCreateEvent extends Event { + 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; + }; +} + +export interface GuildRoleDeleteEvent extends Event { + event: "GUILD_ROLE_DELETE"; + data: { + guild_id: string; + role_id: string; + }; +} + +export interface InviteCreateEvent extends Event { + event: "INVITE_CREATE"; + data: Omit<Invite, "guild" | "channel"> & { + channel_id: string; + guild_id?: string; + }; +} + +export interface InviteDeleteEvent extends Event { + event: "INVITE_DELETE"; + data: { + channel_id: string; + guild_id?: string; + code: string; + }; +} + +export type MessagePayload = Omit<Message, "author_id"> & { + channel_id: string; + guild_id?: string; + author: PublicUser; + member: PublicMember; + mentions: (PublicUser & { member: PublicMember })[]; +}; + +export interface MessageCreateEvent extends Event { + event: "MESSAGE_CREATE"; + data: MessagePayload; +} + +export interface MessageUpdateEvent extends Event { + event: "MESSAGE_UPDATE"; + data: MessagePayload; +} + +export interface MessageDeleteEvent extends Event { + 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; + }; +} + +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; + }; +} + +export interface MessageReactionRemoveEvent extends Event { + 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; + }; +} + +export interface MessageReactionRemoveEmojiEvent extends Event { + 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; +} + +export interface TypingStartEvent extends Event { + 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: User; +} + +export interface VoiceStateUpdateEvent extends Event { + event: "VOICE_STATE_UPDATE"; + data: VoiceState & { + member: PublicMember; + }; +} + +export interface VoiceServerUpdateEvent extends Event { + event: "VOICE_SERVER_UPDATE"; + data: { + token: string; + guild_id: string; + endpoint: string; + }; +} + +export interface WebhooksUpdateEvent extends Event { + event: "WEBHOOKS_UPDATE"; + data: { + guild_id: string; + channel_id: string; + }; +} + +export type ApplicationCommandPayload = ApplicationCommand & { + guild_id: string; +}; + +export interface ApplicationCommandCreateEvent extends Event { + event: "APPLICATION_COMMAND_CREATE"; + data: ApplicationCommandPayload; +} + +export interface ApplicationCommandUpdateEvent extends Event { + event: "APPLICATION_COMMAND_UPDATE"; + data: ApplicationCommandPayload; +} + +export interface ApplicationCommandDeleteEvent extends Event { + event: "APPLICATION_COMMAND_DELETE"; + data: ApplicationCommandPayload; +} + +export interface InteractionCreateEvent extends Event { + event: "INTERACTION_CREATE"; + data: Interaction; +} + +export interface MessageAckEvent extends Event { + 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: Relationship & { + should_notify?: boolean; + user: PublicUser; + }; +} + +export interface RelationshipRemoveEvent extends Event { + event: "RELATIONSHIP_REMOVE"; + data: Omit<Relationship, "nickname">; +} + +// located in collection events + +export enum EVENTEnum { + Ready = "READY", + ChannelCreate = "CHANNEL_CREATE", + ChannelUpdate = "CHANNEL_UPDATE", + ChannelDelete = "CHANNEL_DELETE", + ChannelPinsUpdate = "CHANNEL_PINS_UPDATE", + 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", + 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", + WebhooksUpdate = "WEBHOOKS_UPDATE", + InteractionCreate = "INTERACTION_CREATE", + VoiceStateUpdate = "VOICE_STATE_UPDATE", + VoiceServerUpdate = "VOICE_SERVER_UPDATE", + ApplicationCommandCreate = "APPLICATION_COMMAND_CREATE", + ApplicationCommandUpdate = "APPLICATION_COMMAND_UPDATE", + ApplicationCommandDelete = "APPLICATION_COMMAND_DELETE", +} + +export type EVENT = + | "READY" + | "CHANNEL_CREATE" + | "CHANNEL_UPDATE" + | "CHANNEL_DELETE" + | "CHANNEL_PINS_UPDATE" + | "GUILD_CREATE" + | "GUILD_UPDATE" + | "GUILD_DELETE" + | "GUILD_BAN_ADD" + | "GUILD_BAN_REMOVE" + | "GUILD_EMOJI_UPDATE" + | "GUILD_INTEGRATIONS_UPDATE" + | "GUILD_MEMBER_ADD" + | "GUILD_MEMBER_REMOVE" + | "GUILD_MEMBER_UPDATE" + | "GUILD_MEMBER_SPEAKING" + | "GUILD_MEMBERS_CHUNK" + | "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" + | "WEBHOOKS_UPDATE" + | "INTERACTION_CREATE" + | "VOICE_STATE_UPDATE" + | "VOICE_SERVER_UPDATE" + | "APPLICATION_COMMAND_CREATE" + | "APPLICATION_COMMAND_UPDATE" + | "APPLICATION_COMMAND_DELETE" + | "MESSAGE_ACK" + | "RELATIONSHIP_ADD" + | "RELATIONSHIP_REMOVE" + | CUSTOMEVENTS; + +export type CUSTOMEVENTS = "INVALIDATED"; diff --git a/util/src/models/Guild.ts b/util/src/models/Guild.ts new file mode 100644 index 00000000..13a7d078 --- /dev/null +++ b/util/src/models/Guild.ts @@ -0,0 +1,161 @@ +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; +import { ChannelModel } from "./Channel"; +import { EmojiModel } from "./Emoji"; +import { MemberModel } from "./Member"; +import { RoleModel } from "./Role"; + +export interface GuildDocument extends Document, Guild { + id: string; +} + +export interface Guild { + id: string; + afk_channel_id?: string; + afk_timeout?: number; + application_id?: string; + banner?: string; + default_message_notifications?: number; + description?: string; + discovery_splash?: string; + explicit_content_filter?: number; + features: string[]; + icon?: string; + large?: boolean; + max_members?: number; // e.g. default 100.000 + max_presences?: number; + max_video_channel_users?: number; // ? default: 25, is this max 25 streaming or watching + member_count?: number; + presence_count?: number; // users online + // members?: Member[]; // * Members are stored in a seperate collection + // roles: Role[]; // * Role are stored in a seperate collection + // channels: GuildChannel[]; // * Channels are stored in a seperate collection + // emojis: Emoji[]; // * Emojis are stored in a seperate collection + // voice_states: []; // * voice_states are stored in a seperate collection + //TODO: + presences?: object[]; + mfa_level?: number; + name: string; + owner_id: string; + preferred_locale?: string; // only community guilds can choose this + premium_subscription_count?: number; + premium_tier?: number; // nitro boost level + public_updates_channel_id?: string; + region?: string; + rules_channel_id?: string; + splash?: string; + system_channel_flags?: number; + system_channel_id?: string; + unavailable?: boolean; + vanity_url?: { + code: string; + uses: number; + }; + verification_level?: number; + welcome_screen: { + enabled: boolean; + description: string; + welcome_channels: { + description: string; + emoji_id?: string; + emoji_name: string; + channel_id: string }[]; + }; + widget_channel_id?: string; + widget_enabled?: boolean; +} + +export const GuildSchema = new Schema({ + id: { type: String, required: true }, + afk_channel_id: String, + afk_timeout: Number, + application_id: String, + banner: String, + default_message_notifications: Number, + description: String, + discovery_splash: String, + explicit_content_filter: Number, + features: { type: [String], default: [] }, + icon: String, + large: Boolean, + max_members: { type: Number, default: 100000 }, + max_presences: Number, + max_video_channel_users: { type: Number, default: 25 }, + member_count: Number, + presences: { type: [Object], default: [] }, + presence_count: Number, + mfa_level: Number, + name: { type: String, required: true }, + owner_id: { type: String, required: true }, + preferred_locale: String, + premium_subscription_count: Number, + premium_tier: Number, + public_updates_channel_id: String, + region: String, + rules_channel_id: String, + splash: String, + system_channel_flags: Number, + system_channel_id: String, + unavailable: Boolean, + vanity_url: { + code: String, + uses: Number + }, + verification_level: Number, + voice_states: { type: [Object], default: [] }, + welcome_screen: { + enabled: Boolean, + description: String, + welcome_channels: [{ + description: String, + emoji_id: String, + emoji_name: String, + channel_id: String }], + }, + widget_channel_id: String, + widget_enabled: Boolean, +}); + +GuildSchema.virtual("channels", { + ref: ChannelModel, + localField: "id", + foreignField: "guild_id", + justOne: false, + autopopulate: true, +}); + +GuildSchema.virtual("roles", { + ref: RoleModel, + localField: "id", + foreignField: "guild_id", + justOne: false, + autopopulate: true, +}); + +// nested populate is needed for member users: https://gist.github.com/yangsu/5312204 +GuildSchema.virtual("members", { + ref: MemberModel, + localField: "id", + foreignField: "guild_id", + justOne: false, +}); + +GuildSchema.virtual("emojis", { + ref: EmojiModel, + localField: "id", + foreignField: "guild_id", + justOne: false, + autopopulate: true, +}); + +GuildSchema.virtual("joined_at", { + ref: MemberModel, + localField: "id", + foreignField: "guild_id", + justOne: true, +}).get((member: any, virtual: any, doc: any) => { + return member?.joined_at; +}); + +// @ts-ignore +export const GuildModel = db.model<GuildDocument>("Guild", GuildSchema, "guilds"); diff --git a/util/src/models/Interaction.ts b/util/src/models/Interaction.ts new file mode 100644 index 00000000..764247a5 --- /dev/null +++ b/util/src/models/Interaction.ts @@ -0,0 +1,32 @@ +import { AllowedMentions, Embed } from "./Message"; + +export interface Interaction { + id: string; + type: InteractionType; + data?: {}; + guild_id: string; + channel_id: string; + member_id: string; + token: string; + version: number; +} + +export enum InteractionType { + Ping = 1, + ApplicationCommand = 2, +} + +export enum InteractionResponseType { + Pong = 1, + Acknowledge = 2, + ChannelMessage = 3, + ChannelMessageWithSource = 4, + AcknowledgeWithSource = 5, +} + +export interface InteractionApplicationCommandCallbackData { + tts?: boolean; + content: string; + embeds?: Embed[]; + allowed_mentions?: AllowedMentions; +} diff --git a/util/src/models/Invite.ts b/util/src/models/Invite.ts new file mode 100644 index 00000000..01f12003 --- /dev/null +++ b/util/src/models/Invite.ts @@ -0,0 +1,95 @@ +import { Schema, Document, Types } from "mongoose"; +import db from "../util/Database"; +import { ChannelModel } from "./Channel"; +import { PublicUserProjection, UserModel } from "./User"; +import { GuildModel } from "./Guild"; + +export interface Invite { + code: string; + temporary: boolean; + uses: number; + max_uses: number; + max_age: number; + created_at: Date; + expires_at: Date; + guild_id: string; + channel_id: string; + inviter_id: string; + + // ? What is this? + target_user_id?: string; + target_user_type?: number; +} + +export interface InviteDocument extends Invite, Document {} + +export const InviteSchema = new Schema({ + code: String, + temporary: Boolean, + uses: Number, + max_uses: Number, + max_age: Number, + created_at: Date, + expires_at: Date, + guild_id: String, + channel_id: String, + inviter_id: String, + + // ? What is this? + target_user_id: String, + target_user_type: Number, +}); + +InviteSchema.virtual("channel", { + ref: ChannelModel, + localField: "channel_id", + foreignField: "id", + justOne: true, + autopopulate: { + select: { + id: true, + name: true, + type: true, + }, + }, +}); + +InviteSchema.virtual("inviter", { + ref: UserModel, + localField: "inviter_id", + foreignField: "id", + justOne: true, + autopopulate: { + select: PublicUserProjection, + }, +}); + +InviteSchema.virtual("guild", { + ref: GuildModel, + localField: "guild_id", + foreignField: "id", + justOne: true, + autopopulate: { + select: { + id: true, + name: true, + splash: true, + banner: true, + description: true, + icon: true, + features: true, + verification_level: true, + vanity_url_code: true, + welcome_screen: true, + nsfw: true, + + // TODO: hide the following entries: + // channels: false, + // roles: false, + // emojis: false, + }, + }, +}); + +// @ts-ignore +export const InviteModel = db.model<InviteDocument>("Invite", InviteSchema, "invites"); diff --git a/util/src/models/Member.ts b/util/src/models/Member.ts new file mode 100644 index 00000000..d1c9ad9b --- /dev/null +++ b/util/src/models/Member.ts @@ -0,0 +1,109 @@ +import { PublicUser, PublicUserProjection, User, UserModel } from "./User"; +import { Schema, Types, Document } from "mongoose"; +import db from "../util/Database"; + +export const PublicMemberProjection = { + id: true, + guild_id: true, + nick: true, + roles: true, + joined_at: true, + pending: true, + deaf: true, + mute: true, + premium_since: true, +}; + +export interface Member { + id: string; + guild_id: string; + nick?: string; + roles: string[]; + joined_at: Date; + premium_since?: number; + deaf: boolean; + mute: boolean; + pending: boolean; + settings: UserGuildSettings; + read_state: Record<string, string | null>; + // virtual + user?: User; +} + +export interface MemberDocument extends Member, Document { + id: string; +} + +export interface UserGuildSettings { + channel_overrides: { + channel_id: string; + message_notifications: number; + mute_config: MuteConfig; + muted: boolean; + }[]; + message_notifications: number; + mobile_push: boolean; + mute_config: MuteConfig; + muted: boolean; + suppress_everyone: boolean; + suppress_roles: boolean; + version: number; +} + +export interface MuteConfig { + end_time: number; + selected_time_window: number; +} + +const MuteConfig = { + end_time: Number, + selected_time_window: Number, +}; + +export const MemberSchema = new Schema({ + id: { type: String, required: true }, + guild_id: String, + nick: String, + roles: [String], + joined_at: Date, + premium_since: Number, + deaf: Boolean, + mute: Boolean, + pending: Boolean, + read_state: Object, + settings: { + channel_overrides: [ + { + channel_id: String, + message_notifications: Number, + mute_config: MuteConfig, + muted: Boolean, + }, + ], + message_notifications: Number, + mobile_push: Boolean, + mute_config: MuteConfig, + muted: Boolean, + suppress_everyone: Boolean, + suppress_roles: Boolean, + version: Number, + }, +}); + +MemberSchema.virtual("user", { + ref: UserModel, + localField: "id", + foreignField: "id", + justOne: true, + autopopulate: { + select: PublicUserProjection, + }, +}); + +// @ts-ignore +export const MemberModel = db.model<MemberDocument>("Member", MemberSchema, "members"); + +// @ts-ignore +export interface PublicMember extends Omit<Member, "settings" | "id" | "read_state"> { + user: PublicUser; +} diff --git a/util/src/models/Message.ts b/util/src/models/Message.ts new file mode 100644 index 00000000..15a6f40d --- /dev/null +++ b/util/src/models/Message.ts @@ -0,0 +1,368 @@ +import { Schema, Types, Document } from "mongoose"; +import db from "../util/Database"; +import { PublicUser, PublicUserProjection, UserModel } from "./User"; +import { MemberModel, PublicMember } from "./Member"; +import { Role, RoleModel } from "./Role"; +import { Channel } from "./Channel"; +import { Snowflake } from "../util"; +import { InteractionType } from "./Interaction"; + +export interface Message { + id: string; + channel_id: string; + guild_id?: string; + author_id?: string; + webhook_id?: string; + application_id?: string; + content?: string; + timestamp: Date; + edited_timestamp: Date | null; + tts?: boolean; + mention_everyone?: boolean; + mention_user_ids: string[]; + mention_role_ids: string[]; + mention_channels_ids: string[]; + attachments: Attachment[]; + embeds: Embed[]; + reactions: Reaction[]; + nonce?: string | number; + pinned?: boolean; + type: MessageType; + activity?: { + type: number; + party_id: string; + }; + flags?: bigint; + stickers?: any[]; + message_reference?: { + message_id: string; + channel_id?: string; + guild_id?: string; + }; + interaction?: { + id: string; + type: InteractionType; + name: string; + user_id: string; // the user who invoked the interaction + // user: User; // TODO: autopopulate user + }; + components: MessageComponent[]; + + // * mongoose virtuals: + // TODO: + // application: Application; // TODO: auto pouplate application + author?: PublicUser; + member?: PublicMember; + mentions?: (PublicUser & { + member: PublicMember; + })[]; + mention_roles?: Role[]; + mention_channels?: Channel[]; + created_at?: Date; + // thread // TODO +} + +const PartialEmoji = { + id: String, + name: { type: String, required: true }, + animated: { type: Boolean, required: true }, +}; + +const MessageComponent: any = { + type: { type: Number, required: true }, + style: Number, + label: String, + emoji: PartialEmoji, + custom_id: String, + url: String, + disabled: Boolean, + components: [Object], +}; + +export interface MessageComponent { + type: number; + style?: number; + label?: string; + emoji?: PartialEmoji; + custom_id?: string; + url?: string; + disabled?: boolean; + components: MessageComponent[]; +} + +export enum MessageComponentType { + ActionRow = 1, + Button = 2, +} + +export interface MessageDocument extends Document, Message { + id: string; +} + +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, + GUILD_DISCOVERY_DISQUALIFIED = 14, + GUILD_DISCOVERY_REQUALIFIED = 15, + REPLY = 19, + APPLICATION_COMMAND = 20, +} + +export interface Attachment { + id: string; // attachment id + filename: string; // name of file attached + size: number; // size of file in bytes + url: string; // source url of file + proxy_url: string; // a proxied url of file + height?: number; // height of file (if image) + width?: number; // width of file (if image) + content_type?: string; +} + +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; + }[]; +} + +export enum EmbedType { + rich = "rich", + image = "image", + video = "video", + gifv = "gifv", + article = "article", + link = "link", +} + +export interface EmbedImage { + url?: string; + proxy_url?: string; + height?: number; + width?: number; +} + +export interface Reaction { + count: number; + //// not saved in the database // me: boolean; // whether the current user reacted using this emoji + emoji: PartialEmoji; + user_ids: string[]; +} + +export interface PartialEmoji { + id?: string; + name: string; + animated?: boolean; +} + +export interface AllowedMentions { + parse?: ("users" | "roles" | "everyone")[]; + roles?: string[]; + users?: string[]; + replied_user?: boolean; +} + +export const Attachment = { + id: String, // attachment id + filename: String, // name of file attached + size: Number, // size of file in bytes + url: String, // source url of file + proxy_url: String, // a proxied url of file + height: Number, // height of file (if image) + width: Number, // width of file (if image) + content_type: String, +}; + +export const EmbedImage = { + url: String, + proxy_url: String, + height: Number, + width: Number, +}; + +const Reaction = { + count: Number, + user_ids: [String], + emoji: { + id: String, + name: String, + animated: Boolean, + }, +}; + +export const Embed = { + title: String, //title of embed + type: { type: String }, // 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 const MessageSchema = new Schema({ + id: String, + channel_id: String, + author_id: String, + webhook_id: String, + guild_id: String, + application_id: String, + content: String, + timestamp: Date, + edited_timestamp: Date, + tts: Boolean, + mention_everyone: Boolean, + mention_user_ids: [String], + mention_role_ids: [String], + mention_channel_ids: [String], + attachments: [Attachment], + embeds: [Embed], + reactions: [Reaction], + nonce: Schema.Types.Mixed, // can be a long or a string + pinned: Boolean, + type: { type: Number }, + activity: { + type: { type: Number }, + party_id: String, + }, + flags: Types.Long, + stickers: [], + message_reference: { + message_id: String, + channel_id: String, + guild_id: String, + }, + components: [MessageComponent], + // virtual: + // author: { + // ref: UserModel, + // localField: "author_id", + // foreignField: "id", + // justOne: true, + // autopopulate: { select: { id: true, user_data: false } }, + // }, +}); + +MessageSchema.virtual("author", { + ref: UserModel, + localField: "author_id", + foreignField: "id", + justOne: true, + autopopulate: { select: PublicUserProjection }, +}); + +MessageSchema.virtual("member", { + ref: MemberModel, + localField: "author_id", + foreignField: "id", + justOne: true, +}); + +MessageSchema.virtual("mentions", { + ref: UserModel, + localField: "mention_user_ids", + foreignField: "id", + justOne: false, + autopopulate: { select: PublicUserProjection }, +}); + +MessageSchema.virtual("mention_roles", { + ref: RoleModel, + localField: "mention_role_ids", + foreignField: "id", + justOne: false, + autopopulate: true, +}); + +MessageSchema.virtual("mention_channels", { + ref: RoleModel, + localField: "mention_channel_ids", + foreignField: "id", + justOne: false, + autopopulate: { select: { id: true, guild_id: true, type: true, name: true } }, +}); + +MessageSchema.virtual("referenced_message", { + ref: "Message", + localField: "message_reference.message_id", + foreignField: "id", + justOne: true, + autopopulate: true, +}); + +MessageSchema.virtual("created_at").get(function (this: MessageDocument) { + return new Date(Snowflake.deconstruct(this.id).timestamp); +}); + +MessageSchema.set("removeResponse", ["mention_channel_ids", "mention_role_ids", "mention_user_ids", "author_id"]); + +// TODO: missing Application Model +// MessageSchema.virtual("application", { +// ref: Application, +// localField: "mention_role_ids", +// foreignField: "id", +// justOne: true, +// }); + +// @ts-ignore +export const MessageModel = db.model<MessageDocument>("Message", MessageSchema, "messages"); diff --git a/util/src/models/RateLimit.ts b/util/src/models/RateLimit.ts new file mode 100644 index 00000000..6a0e1ffd --- /dev/null +++ b/util/src/models/RateLimit.ts @@ -0,0 +1,25 @@ +import { Schema, Document, Types } from "mongoose"; +import db from "../util/Database"; + +export interface Bucket { + id: "global" | "error" | string; // channel_239842397 | guild_238927349823 | webhook_238923423498 + user_id: string; + hits: number; + blocked: boolean; + expires_at: Date; +} + +export interface BucketDocument extends Bucket, Document { + id: string; +} + +export const BucketSchema = new Schema({ + id: { type: String, required: true }, + user_id: { type: String, required: true }, // bot, user, oauth_application, webhook + hits: { type: Number, required: true }, // Number of times the user hit this bucket + blocked: { type: Boolean, required: true }, + expires_at: { type: Date, required: true }, +}); + +// @ts-ignore +export const BucketModel = db.model<BucketDocument>("Bucket", BucketSchema, "ratelimits"); diff --git a/util/src/models/ReadState.ts b/util/src/models/ReadState.ts new file mode 100644 index 00000000..9c4fb323 --- /dev/null +++ b/util/src/models/ReadState.ts @@ -0,0 +1,26 @@ +import { PublicMember } from "./Member"; +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; + +export interface ReadState extends Document { + message_id: string; + channel_id: string; + user_id: string; + last_message_id?: string; + last_pin_timestamp?: Date; + mention_count: number; + manual: boolean; +} + +export const ReadStateSchema = new Schema({ + message_id: String, + channel_id: String, + user_id: String, + last_message_id: String, + last_pin_timestamp: Date, + mention_count: Number, + manual: Boolean, +}); + +// @ts-ignore +export const ReadStateModel = db.model<ReadState>("ReadState", ReadStateSchema, "readstates"); diff --git a/util/src/models/Role.ts b/util/src/models/Role.ts new file mode 100644 index 00000000..c1111c84 --- /dev/null +++ b/util/src/models/Role.ts @@ -0,0 +1,42 @@ +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; +import toBigInt from "../util/toBigInt"; + +export interface Role { + id: string; + guild_id: string; + color: number; + hoist: boolean; + managed: boolean; + mentionable: boolean; + name: string; + permissions: bigint; + position: number; + tags?: { + bot_id?: string; + }; +} + +export interface RoleDocument extends Document, Role { + id: string; +} + +export const RoleSchema = new Schema({ + id: String, + guild_id: String, + color: Number, + hoist: Boolean, + managed: Boolean, + mentionable: Boolean, + name: String, + permissions: { type: String, get: toBigInt }, + position: Number, + tags: { + bot_id: String, + }, +}); + +RoleSchema.set("removeResponse", ["guild_id"]); + +// @ts-ignore +export const RoleModel = db.model<RoleDocument>("Role", RoleSchema, "roles"); diff --git a/util/src/models/Status.ts b/util/src/models/Status.ts new file mode 100644 index 00000000..5a9bf2ca --- /dev/null +++ b/util/src/models/Status.ts @@ -0,0 +1,13 @@ +export type Status = "idle" | "dnd" | "online" | "offline"; + +export interface ClientStatus { + desktop?: string; // e.g. Windows/Linux/Mac + mobile?: string; // e.g. iOS/Android + web?: string; // e.g. browser, bot account +} + +export const ClientStatus = { + desktop: String, + mobile: String, + web: String, +}; diff --git a/util/src/models/Team.ts b/util/src/models/Team.ts new file mode 100644 index 00000000..795c82d2 --- /dev/null +++ b/util/src/models/Team.ts @@ -0,0 +1,17 @@ +export interface Team { + icon: string | null; + id: string; + members: { + membership_state: number; + permissions: string[]; + team_id: string; + user_id: string; + }[]; + name: string; + owner_user_id: string; +} + +export enum TeamMemberState { + INVITED = 1, + ACCEPTED = 2, +} diff --git a/util/src/models/Template.ts b/util/src/models/Template.ts new file mode 100644 index 00000000..ad0f9104 --- /dev/null +++ b/util/src/models/Template.ts @@ -0,0 +1,51 @@ +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; +import { PublicUser, User, UserModel, PublicUserProjection } from "./User"; +import { Guild, GuildModel } from "./Guild"; + +export interface Template extends Document { + id: string; + code: string; + name: string; + description?: string; + usage_count?: number; + creator_id: string; + creator: User; + created_at: Date; + updated_at: Date; + source_guild_id: String; + serialized_source_guild: Guild; +} + +export const TemplateSchema = new Schema({ + id: String, + code: String, + name: String, + description: String, + usage_count: Number, + creator_id: String, + created_at: Date, + updated_at: Date, + source_guild_id: String, +}); + +TemplateSchema.virtual("creator", { + ref: UserModel, + localField: "creator_id", + foreignField: "id", + justOne: true, + autopopulate: { + select: PublicUserProjection, + }, +}); + +TemplateSchema.virtual("serialized_source_guild", { + ref: GuildModel, + localField: "source_guild_id", + foreignField: "id", + justOne: true, + autopopulate: true, +}); + +// @ts-ignore +export const TemplateModel = db.model<Template>("Template", TemplateSchema, "templates"); diff --git a/util/src/models/User.ts b/util/src/models/User.ts new file mode 100644 index 00000000..c667e954 --- /dev/null +++ b/util/src/models/User.ts @@ -0,0 +1,252 @@ +import { Activity, ActivitySchema } from "./Activity"; +import { ClientStatus, Status } from "./Status"; +import { Schema, Types, Document } from "mongoose"; +import db from "../util/Database"; +import toBigInt from "../util/toBigInt"; + +export const PublicUserProjection = { + username: true, + discriminator: true, + id: true, + public_flags: true, + avatar: true, + accent_color: true, + banner: true, + bio: true, + bot: true, +}; + +export interface User { + id: string; + username: string; // username max length 32, min 2 + discriminator: string; // #0001 4 digit long string from #0001 - #9999 + avatar: string | null; // hash of the user avatar + accent_color: number | null; // banner color of user + banner: string | null; + phone: string | null; // phone number of the user + desktop: boolean; // if the user has desktop app installed + mobile: boolean; // if the user has mobile app installed + premium: boolean; // if user bought nitro + premium_type: number; // nitro level + bot: boolean; // if user is bot + bio: string; // short description of the user (max 190 chars) + system: boolean; // shouldn't be used, the api sents this field type true, if the genetaed message comes from a system generated author + nsfw_allowed: boolean; // if the user is older than 18 (resp. Config) + mfa_enabled: boolean; // if multi factor authentication is enabled + created_at: Date; // registration date + verified: boolean; // if the user is offically verified + disabled: boolean; // if the account is disabled + deleted: boolean; // if the user was deleted + email: string | null; // email of the user + flags: bigint; // UserFlags + public_flags: bigint; + user_settings: UserSettings; + guilds: string[]; // array of guild ids the user is part of + user_data: UserData; + presence: { + status: Status; + activities: Activity[]; + client_status: ClientStatus; + }; +} + +// Private user data: +export interface UserData { + valid_tokens_since: Date; // all tokens with a previous issue date are invalid + relationships: Relationship[]; + connected_accounts: ConnectedAccount[]; + hash: string; // hash of the password, salt is saved in password (bcrypt) + fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts +} + +export interface UserDocument extends User, Document { + id: string; +} + +export interface PublicUser { + id: string; + discriminator: string; + username: string; + avatar: string | null; + accent_color: number; + banner: string | null; + public_flags: bigint; + bot: boolean; +} + +export interface ConnectedAccount { + access_token: string; + friend_sync: boolean; + id: string; + name: string; + revoked: boolean; + show_activity: boolean; + type: string; + verifie: boolean; + visibility: number; +} + +export interface Relationship { + id: string; + nickname?: string; + type: RelationshipType; +} + +export enum RelationshipType { + outgoing = 4, + incoming = 3, + blocked = 2, + friends = 1, +} + +export interface UserSettings { + afk_timeout: number; + allow_accessibility_detection: boolean; + animate_emoji: boolean; + animate_stickers: number; + contact_sync_enabled: boolean; + convert_emoticons: boolean; + custom_status: { + emoji_id: string | null; + emoji_name: string | null; + expires_at: number | null; + text: string | 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_source_flags: { all: boolean }; + gateway_connected: boolean; + gif_auto_play: boolean; + guild_folders: // every top guild is displayed as a "folder" + { + color: number; + guild_ids: string[]; + id: number; + name: string; + }[]; + 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"; + stream_notifications_enabled: boolean; + theme: "dark" | "white"; // dark + timezone_offset: number; // e.g -60 +} + +export const UserSchema = new Schema({ + id: String, + username: String, + discriminator: String, + avatar: String, + accent_color: Number, + banner: String, + phone: String, + desktop: Boolean, + mobile: Boolean, + premium: Boolean, + premium_type: Number, + bot: Boolean, + bio: String, + system: Boolean, + nsfw_allowed: Boolean, + mfa_enabled: Boolean, + created_at: Date, + verified: Boolean, + disabled: Boolean, + deleted: Boolean, + email: String, + flags: { type: String, get: toBigInt }, // TODO: automatically convert Types.Long to BitField of UserFlags + public_flags: { type: String, get: toBigInt }, + guilds: [String], // array of guild ids the user is part of + user_data: { + fingerprints: [String], + hash: String, // hash of the password, salt is saved in password (bcrypt) + valid_tokens_since: Date, // all tokens with a previous issue date are invalid + relationships: [ + { + id: { type: String, required: true }, + nickname: String, + type: { type: Number }, + }, + ], + connected_accounts: [ + { + access_token: String, + friend_sync: Boolean, + id: String, + name: String, + revoked: Boolean, + show_activity: Boolean, + type: { type: String }, + verifie: Boolean, + visibility: Number, + }, + ], + }, + user_settings: { + afk_timeout: Number, + allow_accessibility_detection: Boolean, + animate_emoji: Boolean, + animate_stickers: Number, + contact_sync_enabled: Boolean, + convert_emoticons: Boolean, + custom_status: { + emoji_id: String, + emoji_name: String, + expires_at: Number, + text: String, + }, + default_guilds_restricted: Boolean, + detect_platform_accounts: Boolean, + developer_mode: Boolean, + disable_games_tab: Boolean, + enable_tts_command: Boolean, + explicit_content_filter: Number, + friend_source_flags: { all: Boolean }, + gateway_connected: Boolean, + gif_auto_play: Boolean, + // every top guild is displayed as a "folder" + guild_folders: [ + { + color: Number, + guild_ids: [String], + id: Number, + name: String, + }, + ], + 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: String, + stream_notifications_enabled: Boolean, + theme: String, // dark + timezone_offset: Number, // e.g -60, + }, + + presence: { + status: String, + activities: [ActivitySchema], + client_status: ClientStatus, + }, +}); + +// @ts-ignore +export const UserModel = db.model<UserDocument>("User", UserSchema, "users"); diff --git a/util/src/models/VoiceState.ts b/util/src/models/VoiceState.ts new file mode 100644 index 00000000..c1f90edd --- /dev/null +++ b/util/src/models/VoiceState.ts @@ -0,0 +1,34 @@ +import { PublicMember } from "./Member"; +import { Schema, model, Types, Document } from "mongoose"; +import db from "../util/Database"; + +export interface VoiceState extends Document { + guild_id?: string; + channel_id: string; + user_id: string; + session_id: string; + deaf: boolean; + mute: boolean; + self_deaf: boolean; + self_mute: boolean; + self_stream?: boolean; + self_video: boolean; + suppress: boolean; // whether this user is muted by the current user +} + +export const VoiceSateSchema = new Schema({ + guild_id: String, + channel_id: String, + user_id: String, + session_id: String, + deaf: Boolean, + mute: Boolean, + self_deaf: Boolean, + self_mute: Boolean, + self_stream: Boolean, + self_video: Boolean, + suppress: Boolean, // whether this user is muted by the current user +}); + +// @ts-ignore +export const VoiceStateModel = db.model<VoiceState>("VoiceState", VoiceSateSchema, "voicestates"); diff --git a/util/src/models/Webhook.ts b/util/src/models/Webhook.ts new file mode 100644 index 00000000..7379e98f --- /dev/null +++ b/util/src/models/Webhook.ts @@ -0,0 +1,84 @@ +import { Schema, Document, Types } from "mongoose"; +import { transpileModule } from "typescript"; +import db from "../util/Database"; +import { ChannelModel } from "./Channel"; +import { GuildModel } from "./Guild"; + +export interface Webhook {} + +export enum WebhookType { + Incoming = 1, + ChannelFollower = 2, +} + +export interface WebhookDocument extends Document, Webhook { + id: String; + type: number; + guild_id?: string; + channel_id: string; + name?: string; + avatar?: string; + token?: string; + application_id?: string; + user_id?: string; + source_guild_id: string; +} + +export const WebhookSchema = new Schema({ + id: { type: String, required: true }, + type: { type: Number, required: true }, + guild_id: String, + channel_id: String, + name: String, + avatar: String, + token: String, + application_id: String, + user_id: String, + source_guild_id: String, + source_channel_id: String, +}); + +WebhookSchema.virtual("source_guild", { + ref: GuildModel, + localField: "id", + foreignField: "source_guild_id", + justOne: true, + autopopulate: { + select: { + icon: true, + id: true, + name: true, + }, + }, +}); + +WebhookSchema.virtual("source_channel", { + ref: ChannelModel, + localField: "id", + foreignField: "source_channel_id", + justOne: true, + autopopulate: { + select: { + id: true, + name: true, + }, + }, +}); + +WebhookSchema.virtual("source_channel", { + ref: ChannelModel, + localField: "id", + foreignField: "source_channel_id", + justOne: true, + autopopulate: { + select: { + id: true, + name: true, + }, + }, +}); + +WebhookSchema.set("removeResponse", ["source_channel_id", "source_guild_id"]); + +// @ts-ignore +export const WebhookModel = db.model<WebhookDocument>("Webhook", WebhookSchema, "webhooks"); diff --git a/util/src/models/index.ts b/util/src/models/index.ts new file mode 100644 index 00000000..d0a46bf9 --- /dev/null +++ b/util/src/models/index.ts @@ -0,0 +1,89 @@ +import mongoose, { Schema, Document } from "mongoose"; +import mongooseAutoPopulate from "mongoose-autopopulate"; + +type UpdateWithAggregationPipeline = UpdateAggregationStage[]; +type UpdateAggregationStage = + | { $addFields: any } + | { $set: any } + | { $project: any } + | { $unset: any } + | { $replaceRoot: any } + | { $replaceWith: any }; +type EnforceDocument<T, TMethods> = T extends Document ? T : T & Document & TMethods; + +declare module "mongoose" { + interface Model<T, TQueryHelpers = {}, TMethods = {}> { + // removed null -> always return document -> throw error if it doesn't exist + findOne( + filter?: FilterQuery<T>, + projection?: any | null, + options?: QueryOptions | null, + callback?: (err: CallbackError, doc: EnforceDocument<T, TMethods>) => void + ): QueryWithHelpers<EnforceDocument<T, TMethods>, EnforceDocument<T, TMethods>, TQueryHelpers>; + findOneAndUpdate( + filter?: FilterQuery<T>, + update?: UpdateQuery<T> | UpdateWithAggregationPipeline, + options?: QueryOptions | null, + callback?: (err: any, doc: EnforceDocument<T, TMethods> | null, res: any) => void + ): QueryWithHelpers<EnforceDocument<T, TMethods>, EnforceDocument<T, TMethods>, TQueryHelpers>; + } +} + +var HTTPError: any; + +try { + HTTPError = require("lambert-server").HTTPError; +} catch (e) { + HTTPError = Error; +} + +mongoose.plugin(mongooseAutoPopulate); + +mongoose.plugin((schema: Schema, opts: any) => { + schema.set("toObject", { + virtuals: true, + versionKey: false, + transform(doc: any, ret: any) { + delete ret._id; + delete ret.__v; + const props = schema.get("removeResponse") || []; + props.forEach((prop: string) => { + delete ret[prop]; + }); + }, + }); + schema.post("findOne", function (doc, next) { + try { + // @ts-ignore + const isExistsQuery = JSON.stringify(this._userProvidedFields) === JSON.stringify({ _id: 1 }); + if (!doc && !isExistsQuery) { + // @ts-ignore + return next(new HTTPError(`${this?.mongooseCollection?.name}.${this?._conditions?.id} not found`, 400)); + } + // @ts-ignore + return next(); + } catch (error) { + // @ts-ignore + next(); + } + }); +}); + +export * from "./Activity"; +export * from "./Application"; +export * from "./Ban"; +export * from "./Channel"; +export * from "./Emoji"; +export * from "./Event"; +export * from "./Template"; +export * from "./Guild"; +export * from "./Invite"; +export * from "./Interaction"; +export * from "./Member"; +export * from "./Message"; +export * from "./Status"; +export * from "./Role"; +export * from "./User"; +export * from "./VoiceState"; +export * from "./ReadState"; +export * from "./RateLimit"; diff --git a/util/src/util/BitField.ts b/util/src/util/BitField.ts new file mode 100644 index 00000000..728dc632 --- /dev/null +++ b/util/src/util/BitField.ts @@ -0,0 +1,143 @@ +"use strict"; + +// https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah + +export type BitFieldResolvable = number | BigInt | BitField | string | BitFieldResolvable[]; + +/** + * Data structure that makes it easy to interact with a bitfield. + */ +export class BitField { + public bitfield: bigint = BigInt(0); + + public static FLAGS: Record<string, bigint> = {}; + + 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)) !== 0n; + } + + /** + * 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; + } + + /** + * 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<BitField> { + 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 = 0n; + 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 = 0n; + 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<string, boolean> = {}; + 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)); + } + + toJSON() { + return this.bitfield; + } + + valueOf() { + return this.bitfield; + } + + *[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 + */ + + /** + * Resolves bitfields to their numeric form. + * @param {BitFieldResolvable} [bit=0] - bit(s) to resolve + * @returns {number} + */ + static resolve(bit: BitFieldResolvable = 0n): bigint { + // @ts-ignore + const FLAGS = this.FLAGS || this.constructor?.FLAGS; + if ((typeof bit === "number" || typeof bit === "bigint") && bit >= 0n) return BigInt(bit); + if (bit instanceof BitField) return bit.bitfield; + if (Array.isArray(bit)) { + // @ts-ignore + const resolve = this.constructor?.resolve || this.resolve; + return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), 0n); + } + if (typeof bit === "string" && typeof FLAGS[bit] !== "undefined") return FLAGS[bit]; + throw new RangeError("BITFIELD_INVALID: " + bit); + } +} diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts new file mode 100644 index 00000000..78b44315 --- /dev/null +++ b/util/src/util/Config.ts @@ -0,0 +1,284 @@ +import { Schema, model, Types, Document } from "mongoose"; +import "missing-native-js-functions"; +import db, { MongooseCache } from "./Database"; +import { Snowflake } from "./Snowflake"; +import crypto from "crypto"; + +var config: any; + +export default { + init: async function init(defaultOpts: any = DefaultOptions) { + config = await db.collection("config").findOne({}); + return this.set((config || {}).merge(defaultOpts)); + }, + get: function get() { + return config as DefaultOptions; + }, + set: function set(val: any) { + return db.collection("config").updateOne({}, { $set: val }, { upsert: true }); + }, +}; + +export interface RateLimitOptions { + bot?: number; + count: number; + window: number; + onyIp?: boolean; +} + +export interface Region { + id: string; + name: string; + vip: boolean; + custom: boolean; + deprecated: boolean; + optimal: boolean; +} + +export interface KafkaBroker { + ip: string; + port: number; +} + +export interface DefaultOptions { + gateway: { + endpoint: string | null; + }; + cdn: { + endpoint: string | null; + }; + general: { + instance_id: string; + }; + permissions: { + user: { + createGuilds: boolean; + }; + }; + limits: { + user: { + maxGuilds: number; + maxUsername: number; + maxFriends: number; + }; + guild: { + maxRoles: number; + maxMembers: number; + maxChannels: number; + maxChannelsInCategory: number; + hideOfflineMember: number; + }; + message: { + maxCharacters: number; + maxTTSCharacters: number; + maxReactions: number; + maxAttachmentSize: number; + maxBulkDelete: number; + }; + channel: { + maxPins: number; + maxTopic: number; + }; + rate: { + ip: Omit<RateLimitOptions, "bot_count">; + global: RateLimitOptions; + error: RateLimitOptions; + routes: { + guild: RateLimitOptions; + webhook: RateLimitOptions; + channel: RateLimitOptions; + auth: { + login: RateLimitOptions; + register: RateLimitOptions; + }; + // TODO: rate limit configuration for all routes + }; + }; + }; + security: { + requestSignature: string; + jwtSecret: string; + forwadedFor: string | null; // header to get the real user ip address + captcha: { + enabled: boolean; + service: "recaptcha" | "hcaptcha" | null; // TODO: hcaptcha, custom + sitekey: string | null; + secret: string | null; + }; + ipdataApiKey: string | null; + }; + login: { + requireCaptcha: boolean; + }; + register: { + email: { + necessary: boolean; // we have to use necessary instead of required as the cli tool uses json schema and can't use required + allowlist: boolean; + blocklist: boolean; + domains: string[]; + }; + dateOfBirth: { + necessary: boolean; + minimum: number; // in years + }; + requireCaptcha: boolean; + requireInvite: boolean; + allowNewRegistration: boolean; + allowMultipleAccounts: boolean; + blockProxies: boolean; + password: { + minLength: number; + minNumbers: number; + minUpperCase: number; + minSymbols: number; + }; + }; + regions: { + default: string; + available: Region[]; + }; + rabbitmq: { + host: string | null; + }; + kafka: { + brokers: KafkaBroker[] | null; + }; +} + +export const DefaultOptions: DefaultOptions = { + gateway: { + endpoint: null, + }, + cdn: { + endpoint: null, + }, + general: { + instance_id: Snowflake.generate(), + }, + permissions: { + user: { + createGuilds: true, + }, + }, + limits: { + user: { + maxGuilds: 100, + maxUsername: 32, + maxFriends: 1000, + }, + guild: { + maxRoles: 250, + maxMembers: 250000, + maxChannels: 500, + maxChannelsInCategory: 50, + hideOfflineMember: 1000, + }, + message: { + maxCharacters: 2000, + maxTTSCharacters: 200, + maxReactions: 20, + maxAttachmentSize: 8388608, + maxBulkDelete: 100, + }, + channel: { + maxPins: 50, + maxTopic: 1024, + }, + rate: { + ip: { + count: 500, + window: 5, + }, + global: { + count: 20, + window: 5, + bot: 250, + }, + error: { + count: 10, + window: 5, + }, + routes: { + guild: { + count: 5, + window: 5, + }, + webhook: { + count: 5, + window: 5, + }, + channel: { + count: 5, + window: 5, + }, + auth: { + login: { + count: 5, + window: 60, + }, + register: { + count: 2, + window: 60 * 60 * 12, + }, + }, + }, + }, + }, + security: { + requestSignature: crypto.randomBytes(32).toString("base64"), + jwtSecret: crypto.randomBytes(256).toString("base64"), + forwadedFor: null, + // forwadedFor: "X-Forwarded-For" // nginx/reverse proxy + // forwadedFor: "CF-Connecting-IP" // cloudflare: + captcha: { + enabled: false, + service: null, + sitekey: null, + secret: null, + }, + ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9", + }, + login: { + requireCaptcha: false, + }, + register: { + email: { + necessary: true, + allowlist: false, + blocklist: true, + domains: [], // TODO: efficiently save domain blocklist in database + // domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"), + }, + dateOfBirth: { + necessary: true, + minimum: 13, + }, + requireInvite: false, + requireCaptcha: true, + allowNewRegistration: true, + allowMultipleAccounts: true, + blockProxies: true, + password: { + minLength: 8, + minNumbers: 2, + minUpperCase: 2, + minSymbols: 0, + }, + }, + regions: { + default: "fosscord", + available: [{ id: "fosscord", name: "Fosscord", vip: false, custom: false, deprecated: false, optimal: false }], + }, + rabbitmq: { + host: null, + }, + kafka: { + brokers: null, + }, +}; + +export const ConfigSchema = new Schema({}, { strict: false }); + +export interface DefaultOptionsDocument extends DefaultOptions, Document {} + +export const ConfigModel = model<DefaultOptionsDocument>("Config", ConfigSchema, "config"); diff --git a/util/src/util/Constants.ts b/util/src/util/Constants.ts new file mode 100644 index 00000000..a9978c51 --- /dev/null +++ b/util/src/util/Constants.ts @@ -0,0 +1,28 @@ +import { VerifyOptions } from "jsonwebtoken"; + +export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] }; + +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, + GUILD_DISCOVERY_DISQUALIFIED = 14, + GUILD_DISCOVERY_REQUALIFIED = 15, + GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, + GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, + THREAD_CREATED = 18, + REPLY = 19, + APPLICATION_COMMAND = 20, + THREAD_STARTER_MESSAGE = 21, + GUILD_INVITE_REMINDER = 22, +} diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts new file mode 100644 index 00000000..8c6847a8 --- /dev/null +++ b/util/src/util/Database.ts @@ -0,0 +1,151 @@ +import "./MongoBigInt"; +import mongoose, { Collection, Connection, LeanDocument } from "mongoose"; +import { ChangeStream, ChangeEvent, Long } from "mongodb"; +import EventEmitter from "events"; +const uri = process.env.MONGO_URL || "mongodb://localhost:27017/fosscord?readPreference=secondaryPreferred"; +import { URL } from "url"; + +const url = new URL(uri.replace("mongodb://", "http://")); + +const connection = mongoose.createConnection(uri, { + autoIndex: true, + useNewUrlParser: true, + useUnifiedTopology: true, + useFindAndModify: false, +}); +console.log(`[Database] connect: mongodb://${url.username}@${url.host}${url.pathname}${url.search}`); + +export default <Connection>connection; + +function transform<T>(document: T) { + // @ts-ignore + if (!document || !document.toObject) { + try { + // @ts-ignore + delete document._id; + // @ts-ignore + delete document.__v; + } catch (error) {} + return document; + } + // @ts-ignore + return document.toObject({ virtuals: true }); +} + +export function toObject<T>(document: T): LeanDocument<T> { + // @ts-ignore + return Array.isArray(document) ? document.map((x) => transform<T>(x)) : transform(document); +} + +export interface MongooseCache { + on(event: "delete", listener: (id: string) => void): this; + on(event: "change", listener: (data: any) => void): this; + on(event: "insert", listener: (data: any) => void): this; + on(event: "close", listener: () => void): this; +} + +export class MongooseCache extends EventEmitter { + public stream: ChangeStream; + public data: any; + public initalizing?: Promise<void>; + + constructor( + public collection: Collection, + public pipeline: Array<Record<string, unknown>>, + public opts: { + onlyEvents: boolean; + array?: boolean; + } + ) { + super(); + if (this.opts.array == null) this.opts.array = true; + } + + init = () => { + if (this.initalizing) return this.initalizing; + this.initalizing = new Promise(async (resolve, reject) => { + // @ts-ignore + this.stream = this.collection.watch(this.pipeline, { fullDocument: "updateLookup" }); + + this.stream.on("change", this.change); + this.stream.on("close", this.destroy); + this.stream.on("error", console.error); + + if (!this.opts.onlyEvents) { + const arr = await this.collection.aggregate(this.pipeline).toArray(); + if (this.opts.array) this.data = arr || []; + else this.data = arr?.[0]; + } + resolve(); + }); + return this.initalizing; + }; + + changeStream = (pipeline: any) => { + this.pipeline = pipeline; + this.destroy(); + this.init(); + }; + + convertResult = (obj: any) => { + if (obj instanceof Long) return BigInt(obj.toString()); + if (typeof obj === "object") { + Object.keys(obj).forEach((key) => { + obj[key] = this.convertResult(obj[key]); + }); + } + + return obj; + }; + + change = (doc: ChangeEvent) => { + try { + switch (doc.operationType) { + case "dropDatabase": + return this.destroy(); + case "drop": + return this.destroy(); + case "delete": + if (!this.opts.onlyEvents) { + if (this.opts.array) { + this.data = this.data.filter((x: any) => doc.documentKey?._id?.equals(x._id)); + } else this.data = null; + } + return this.emit("delete", doc.documentKey._id.toHexString()); + case "insert": + if (!this.opts.onlyEvents) { + if (this.opts.array) this.data.push(doc.fullDocument); + else this.data = doc.fullDocument; + } + return this.emit("insert", doc.fullDocument); + case "update": + case "replace": + if (!this.opts.onlyEvents) { + if (this.opts.array) { + const i = this.data.findIndex((x: any) => doc.fullDocument?._id?.equals(x._id)); + if (i == -1) this.data.push(doc.fullDocument); + else this.data[i] = doc.fullDocument; + } else this.data = doc.fullDocument; + } + + return this.emit("change", doc.fullDocument); + case "invalidate": + return this.destroy(); + default: + return; + } + } catch (error) { + this.emit("error", error); + } + }; + + destroy = () => { + this.data = null; + this.stream?.off("change", this.change); + this.emit("close"); + + if (this.stream.isClosed()) return; + + return this.stream.close(); + }; +} diff --git a/util/src/util/Intents.ts b/util/src/util/Intents.ts new file mode 100644 index 00000000..943b29cf --- /dev/null +++ b/util/src/util/Intents.ts @@ -0,0 +1,21 @@ +import { BitField } from "./BitField"; + +export class Intents extends BitField { + static FLAGS = { + GUILDS: BigInt(1) << BigInt(0), + GUILD_MEMBERS: BigInt(1) << BigInt(1), + GUILD_BANS: BigInt(1) << BigInt(2), + GUILD_EMOJIS: BigInt(1) << BigInt(3), + GUILD_INTEGRATIONS: BigInt(1) << BigInt(4), + GUILD_WEBHOOKS: BigInt(1) << BigInt(5), + GUILD_INVITES: BigInt(1) << BigInt(6), + GUILD_VOICE_STATES: BigInt(1) << BigInt(7), + GUILD_PRESENCES: BigInt(1) << BigInt(8), + GUILD_MESSAGES: BigInt(1) << BigInt(9), + GUILD_MESSAGE_REACTIONS: BigInt(1) << BigInt(10), + GUILD_MESSAGE_TYPING: BigInt(1) << BigInt(11), + DIRECT_MESSAGES: BigInt(1) << BigInt(12), + DIRECT_MESSAGE_REACTIONS: BigInt(1) << BigInt(13), + DIRECT_MESSAGE_TYPING: BigInt(1) << BigInt(14), + }; +} diff --git a/util/src/util/MessageFlags.ts b/util/src/util/MessageFlags.ts new file mode 100644 index 00000000..c76be4c8 --- /dev/null +++ b/util/src/util/MessageFlags.ts @@ -0,0 +1,14 @@ +// https://github.com/discordjs/discord.js/blob/master/src/util/MessageFlags.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah + +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), + URGENT: BigInt(1) << BigInt(4), + }; +} diff --git a/util/src/util/MongoBigInt.ts b/util/src/util/MongoBigInt.ts new file mode 100644 index 00000000..fc451925 --- /dev/null +++ b/util/src/util/MongoBigInt.ts @@ -0,0 +1,82 @@ +import mongoose from "mongoose"; + +class LongSchema extends mongoose.SchemaType { + public $conditionalHandlers = { + $lt: this.handleSingle, + $lte: this.handleSingle, + $gt: this.handleSingle, + $gte: this.handleSingle, + $ne: this.handleSingle, + $in: this.handleArray, + $nin: this.handleArray, + $mod: this.handleArray, + $all: this.handleArray, + $bitsAnySet: this.handleArray, + $bitsAllSet: this.handleArray, + }; + + handleSingle(val: any) { + return this.cast(val, null, null, "handle"); + } + + handleArray(val: any) { + var self = this; + return val.map(function (m: any) { + return self.cast(m, null, null, "handle"); + }); + } + + checkRequired(val: any) { + return null != val; + } + + cast(val: any, scope?: any, init?: any, type?: string) { + if (null === val) return val; + if ("" === val) return null; + if (typeof val === "bigint") { + return mongoose.mongo.Long.fromString(val.toString()); + } + + if (val instanceof mongoose.mongo.Long) { + if (type === "handle" || init == false) return val; + return BigInt(val.toString()); + } + if (val instanceof Number || "number" == typeof val) return BigInt(val); + if (!Array.isArray(val) && val.toString) return BigInt(val.toString()); + + //@ts-ignore + throw new SchemaType.CastError("Long", val); + } + + castForQuery($conditional: string, value: any) { + var handler; + if (2 === arguments.length) { + // @ts-ignore + handler = this.$conditionalHandlers[$conditional]; + if (!handler) { + throw new Error("Can't use " + $conditional + " with Long."); + } + return handler.call(this, value); + } else { + return this.cast($conditional, null, null, "query"); + } + } +} + +LongSchema.cast = mongoose.SchemaType.cast; +LongSchema.set = mongoose.SchemaType.set; +LongSchema.get = mongoose.SchemaType.get; + +declare module "mongoose" { + namespace Types { + class Long extends mongoose.mongo.Long {} + } + namespace Schema { + namespace Types { + class Long extends LongSchema {} + } + } +} + +mongoose.Schema.Types.Long = LongSchema; +mongoose.Types.Long = mongoose.mongo.Long; diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts new file mode 100644 index 00000000..445e901f --- /dev/null +++ b/util/src/util/Permissions.ts @@ -0,0 +1,262 @@ +// https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah +import { MemberDocument, MemberModel } from "../models/Member"; +import { ChannelDocument, ChannelModel } from "../models/Channel"; +import { ChannelPermissionOverwrite } from "../models/Channel"; +import { Role, RoleDocument, RoleModel } from "../models/Role"; +import { BitField } from "./BitField"; +import { GuildDocument, GuildModel } from "../models/Guild"; +// TODO: check role hierarchy permission + +var HTTPError: any; + +try { + HTTPError = require("lambert-server").HTTPError; +} catch (e) { + HTTPError = Error; +} + +export type PermissionResolvable = bigint | number | Permissions | PermissionResolvable[] | PermissionString; + +type PermissionString = + | "CREATE_INSTANT_INVITE" + | "KICK_MEMBERS" + | "BAN_MEMBERS" + | "ADMINISTRATOR" + | "MANAGE_CHANNELS" + | "MANAGE_GUILD" + | "ADD_REACTIONS" + | "VIEW_AUDIT_LOG" + | "PRIORITY_SPEAKER" + | "STREAM" + | "VIEW_CHANNEL" + | "SEND_MESSAGES" + | "SEND_TTS_MESSAGES" + | "MANAGE_MESSAGES" + | "EMBED_LINKS" + | "ATTACH_FILES" + | "READ_MESSAGE_HISTORY" + | "MENTION_EVERYONE" + | "USE_EXTERNAL_EMOJIS" + | "VIEW_GUILD_INSIGHTS" + | "CONNECT" + | "SPEAK" + | "MUTE_MEMBERS" + | "DEAFEN_MEMBERS" + | "MOVE_MEMBERS" + | "USE_VAD" + | "CHANGE_NICKNAME" + | "MANAGE_NICKNAMES" + | "MANAGE_ROLES" + | "MANAGE_WEBHOOKS" + | "MANAGE_EMOJIS"; + +const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(48); // 16 free custom permission bits, and 16 for discord to add new ones + +export class Permissions extends BitField { + cache: PermissionCache = {}; + + static FLAGS = { + CREATE_INSTANT_INVITE: BigInt(1) << BigInt(0), + KICK_MEMBERS: BigInt(1) << BigInt(1), + BAN_MEMBERS: BigInt(1) << BigInt(2), + ADMINISTRATOR: BigInt(1) << BigInt(3), + MANAGE_CHANNELS: BigInt(1) << BigInt(4), + MANAGE_GUILD: BigInt(1) << BigInt(5), + ADD_REACTIONS: BigInt(1) << BigInt(6), + VIEW_AUDIT_LOG: BigInt(1) << BigInt(7), + PRIORITY_SPEAKER: BigInt(1) << BigInt(8), + STREAM: BigInt(1) << BigInt(9), + VIEW_CHANNEL: BigInt(1) << BigInt(10), + SEND_MESSAGES: BigInt(1) << BigInt(11), + SEND_TTS_MESSAGES: BigInt(1) << BigInt(12), + MANAGE_MESSAGES: BigInt(1) << BigInt(13), + EMBED_LINKS: BigInt(1) << BigInt(14), + ATTACH_FILES: BigInt(1) << BigInt(15), + READ_MESSAGE_HISTORY: BigInt(1) << BigInt(16), + MENTION_EVERYONE: BigInt(1) << BigInt(17), + USE_EXTERNAL_EMOJIS: BigInt(1) << BigInt(18), + VIEW_GUILD_INSIGHTS: BigInt(1) << BigInt(19), + CONNECT: BigInt(1) << BigInt(20), + SPEAK: BigInt(1) << BigInt(21), + MUTE_MEMBERS: BigInt(1) << BigInt(22), + DEAFEN_MEMBERS: BigInt(1) << BigInt(23), + MOVE_MEMBERS: BigInt(1) << BigInt(24), + USE_VAD: BigInt(1) << BigInt(25), + CHANGE_NICKNAME: BigInt(1) << BigInt(26), + MANAGE_NICKNAMES: BigInt(1) << BigInt(27), + MANAGE_ROLES: BigInt(1) << BigInt(28), + MANAGE_WEBHOOKS: BigInt(1) << BigInt(29), + MANAGE_EMOJIS: BigInt(1) << BigInt(30), + /** + * 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 + }; + + 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, but throws an Error if user fails to match auth criteria. + */ + hasThrow(permission: PermissionResolvable) { + if (this.has(permission) && this.has("VIEW_CHANNEL")) return true; + // @ts-ignore + throw new HTTPError(`You are missing the following permissions ${permission}`, 403); + } + + overwriteChannel(overwrites: ChannelPermissionOverwrite[]) { + if (!this.cache) throw new Error("permission chache not available"); + overwrites = overwrites.filter((x) => { + if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id)) return true; + if (x.type === 1 && 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 || 0n); + } + + static rolePermission(roles: Role[]) { + // adds all permissions of all roles together (Bit OR) + return roles.reduce((permission, role) => permission | BigInt(role.permissions), 0n); + } + + static finalPermission({ + user, + guild, + channel, + }: { + user: { id: string; roles: string[] }; + guild: { roles: Role[] }; + channel?: { + overwrites?: ChannelPermissionOverwrite[]; + recipient_ids?: string[] | null; + owner_id?: string; + }; + }) { + if (user.id === "0") return new Permissions("ADMINISTRATOR"); // system user id + + let roles = guild.roles.filter((x) => user.roles.includes(x.id)); + let permission = Permissions.rolePermission(roles); + + if (channel?.overwrites) { + let overwrites = channel.overwrites.filter((x) => { + if (x.type === 0 && user.roles.includes(x.id)) return true; + if (x.type === 1 && 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", + ]); + } + + return new Permissions(); + } + + return new Permissions(permission); + } +} + +export type PermissionCache = { + channel?: ChannelDocument | null; + member?: MemberDocument | null; + guild?: GuildDocument | null; + roles?: RoleDocument[] | null; + user_id?: string; +}; + +export async function getPermission( + user_id?: string, + guild_id?: string, + channel_id?: string, + cache: PermissionCache = {} +) { + var { channel, member, guild, roles } = cache; + + if (!user_id) throw new HTTPError("User not found"); + + if (channel_id && !channel) { + channel = await ChannelModel.findOne( + { id: channel_id }, + { permission_overwrites: true, recipient_ids: true, owner_id: true, guild_id: true } + ).exec(); + if (!channel) throw new HTTPError("Channel not found", 404); + if (channel.guild_id) guild_id = channel.guild_id; + } + + if (guild_id) { + if (!guild) guild = await GuildModel.findOne({ id: guild_id }, { owner_id: true }).exec(); + if (!guild) throw new HTTPError("Guild not found"); + if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); + + if (!member) member = await MemberModel.findOne({ guild_id, id: user_id }, "roles").exec(); + if (!member) throw new HTTPError("Member not found"); + + if (!roles) roles = await RoleModel.find({ guild_id, id: { $in: member.roles } }).exec(); + } + + var permission = Permissions.finalPermission({ + user: { + id: user_id, + roles: member?.roles || [], + }, + guild: { + roles: roles || [], + }, + channel: { + overwrites: channel?.permission_overwrites, + owner_id: channel?.owner_id, + recipient_ids: channel?.recipient_ids, + }, + }); + + const obj = new Permissions(permission); + + // pass cache to permission for possible future getPermission calls + obj.cache = { guild, member, channel, roles, user_id }; + + return obj; +} diff --git a/util/src/util/RabbitMQ.ts b/util/src/util/RabbitMQ.ts new file mode 100644 index 00000000..9da41990 --- /dev/null +++ b/util/src/util/RabbitMQ.ts @@ -0,0 +1,18 @@ +import amqp, { Connection, Channel } from "amqplib"; +import Config from "./Config"; + +export const RabbitMQ: { connection: Connection | null; channel: Channel | null; init: () => Promise<void> } = { + connection: null, + channel: null, + init: async function () { + 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`); + this.channel = await this.connection.createChannel(); + console.log(`[RabbitMQ] channel created`); + }, +}; diff --git a/util/src/util/Regex.ts b/util/src/util/Regex.ts new file mode 100644 index 00000000..bbd48bca --- /dev/null +++ b/util/src/util/Regex.ts @@ -0,0 +1,3 @@ +export const DOUBLE_WHITE_SPACE = /\s\s+/g; +export const SPECIAL_CHAR = /[@#`:\r\n\t\f\v\p{C}]/gu; +export const CHANNEL_MENTION = /<#(\d+)>/g; diff --git a/util/src/util/Snowflake.ts b/util/src/util/Snowflake.ts new file mode 100644 index 00000000..1d725710 --- /dev/null +++ b/util/src/util/Snowflake.ts @@ -0,0 +1,127 @@ +// @ts-nocheck +import cluster from "cluster"; + +// https://github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah +("use strict"); + +// Discord epoch (2015-01-01T00:00:00.000Z) + +/** + * 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 + + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + + /** + * A Twitter 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 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); + + 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); + } + + return dec; + } + + static generate() { + var time = BigInt(Date.now() - Snowflake.EPOCH) << 22n; + var worker = Snowflake.workerId << 17n; + var process = Snowflake.processId << 12n; + var increment = Snowflake.INCREMENT++; + return (time | worker | process | increment).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; + } +} diff --git a/util/src/util/String.ts b/util/src/util/String.ts new file mode 100644 index 00000000..55f11e8d --- /dev/null +++ b/util/src/util/String.ts @@ -0,0 +1,7 @@ +import { SPECIAL_CHAR } from "./Regex"; + +export function trimSpecial(str?: string): string { + // @ts-ignore + if (!str) return; + return str.replace(SPECIAL_CHAR, "").trim(); +} diff --git a/util/src/util/UserFlags.ts b/util/src/util/UserFlags.ts new file mode 100644 index 00000000..72394eff --- /dev/null +++ b/util/src/util/UserFlags.ts @@ -0,0 +1,22 @@ +// https://github.com/discordjs/discord.js/blob/master/src/util/UserFlags.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah + +import { BitField } from "./BitField"; + +export class UserFlags extends BitField { + static FLAGS = { + DISCORD_EMPLOYEE: BigInt(1) << BigInt(0), + PARTNERED_SERVER_OWNER: BigInt(1) << BigInt(1), + HYPESQUAD_EVENTS: BigInt(1) << BigInt(2), + BUGHUNTER_LEVEL_1: BigInt(1) << BigInt(3), + HOUSE_BRAVERY: BigInt(1) << BigInt(6), + HOUSE_BRILLIANCE: BigInt(1) << BigInt(7), + HOUSE_BALANCE: BigInt(1) << BigInt(8), + EARLY_SUPPORTER: BigInt(1) << BigInt(9), + TEAM_USER: BigInt(1) << BigInt(10), + SYSTEM: BigInt(1) << BigInt(12), + BUGHUNTER_LEVEL_2: BigInt(1) << BigInt(14), + VERIFIED_BOT: BigInt(1) << BigInt(16), + EARLY_VERIFIED_BOT_DEVELOPER: BigInt(1) << BigInt(17), + }; +} diff --git a/util/src/util/checkToken.ts b/util/src/util/checkToken.ts new file mode 100644 index 00000000..91bf08d5 --- /dev/null +++ b/util/src/util/checkToken.ts @@ -0,0 +1,24 @@ +import { JWTOptions } from "./Constants"; +import jwt from "jsonwebtoken"; +import { UserModel } from "../models"; + +export function checkToken(token: string, jwtSecret: string): Promise<any> { + return new Promise((res, rej) => { + token = token.replace("Bot ", ""); // TODO: proper bot support + jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded: any) => { + if (err || !decoded) return rej("Invalid Token"); + + const user = await UserModel.findOne( + { id: decoded.id }, + { "user_data.valid_tokens_since": true, bot: true, disabled: true, deleted: true } + ).exec(); + if (!user) return rej("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 < user.user_data.valid_tokens_since.setSeconds(0, 0)) return rej("Invalid Token"); + if (user.disabled) return rej("User disabled"); + if (user.deleted) return rej("User not found"); + + return res({ decoded, user }); + }); + }); +} diff --git a/util/src/util/index.ts b/util/src/util/index.ts new file mode 100644 index 00000000..7523a6ad --- /dev/null +++ b/util/src/util/index.ts @@ -0,0 +1,9 @@ +export * from "./String"; +export * from "./BitField"; +export * from "./Intents"; +export * from "./MessageFlags"; +export * from "./Permissions"; +export * from "./Snowflake"; +export * from "./UserFlags"; +export * from "./toBigInt"; +export * from "./RabbitMQ"; diff --git a/util/src/util/toBigInt.ts b/util/src/util/toBigInt.ts new file mode 100644 index 00000000..d57c4568 --- /dev/null +++ b/util/src/util/toBigInt.ts @@ -0,0 +1,3 @@ +export default function toBigInt(string: String): BigInt { + return BigInt(string); +} diff --git a/util/tsconfig.json b/util/tsconfig.json new file mode 100644 index 00000000..520774d3 --- /dev/null +++ b/util/tsconfig.json @@ -0,0 +1,70 @@ +{ + "include": ["src/**/*.ts"], + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + "lib": ["ES2020"] /* Specify library files to be included in the compilation. */, + "allowJs": true /* Allow javascript files to be compiled. */, + "checkJs": true /* Report errors in .js files. */, + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true /* Generates corresponding '.d.ts' file. */, + "declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */, + "sourceMap": true /* Generates corresponding '.map' file. */, + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist/" /* Redirect output structure to the directory. */, + "rootDir": "./src/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, + "strictNullChecks": true /* Enable strict null checks. */, + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + "types": ["node"] /* Type declaration files to be included in compilation. */, + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} |