diff --git a/rtc/.gitignore b/rtc/.gitignore
new file mode 100644
index 00000000..f14b4548
--- /dev/null
+++ b/rtc/.gitignore
@@ -0,0 +1,145 @@
+# 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/
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.o
+
+# Protobuffer builds
+*.pb.cc
+*.pb.h
+
+# Directories
+build/
+.vscode/
diff --git a/rtc/.npmignore b/rtc/.npmignore
new file mode 100644
index 00000000..05a9d0cf
--- /dev/null
+++ b/rtc/.npmignore
@@ -0,0 +1 @@
+!dist/
\ No newline at end of file
diff --git a/rtc/.prettierrc b/rtc/.prettierrc
new file mode 100644
index 00000000..d569c548
--- /dev/null
+++ b/rtc/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "tabWidth": 4,
+ "useTabs": true,
+ "printWidth": 120
+}
diff --git a/rtc/CMakeLists.txt b/rtc/CMakeLists.txt
new file mode 100644
index 00000000..2cf5c0a6
--- /dev/null
+++ b/rtc/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 3.2)
+project(fosscord-media)
+
+set(CMAKE_CXX_STANDARD 17)
+
+find_package(Threads REQUIRED)
+
+find_package(mongocxx REQUIRED)
+find_package(Boost REQUIRED)
+
+
+file(GLOB SourceFiles ${PROJECT_SOURCE_DIR}/src/*.cpp)
+#include_directories("bsoncxx/v_noabi/bsoncxx/")
+add_executable(${CMAKE_PROJECT_NAME} ${SourceFiles})
+
+target_link_libraries(${CMAKE_PROJECT_NAME} datachannel mongo::mongocxx_shared Boost::boost)
\ No newline at end of file
diff --git a/rtc/README.md b/rtc/README.md
new file mode 100644
index 00000000..ee452adf
--- /dev/null
+++ b/rtc/README.md
@@ -0,0 +1,18 @@
+# Fosscord-media
+
+A Fosscord media (voice and video) server
+
+## Installation
+
+### Prerequisites
+
+- Install the [libdatachannel](https://github.com/paullouisageneau/libdatachannel) library
+- Install the [libmongocxx](http://mongocxx.org/mongocxx-v3/installation/) driver and its requirements
+
+### Building
+
+```bash
+$ cmake
+$ cd build
+$ make
+```
diff --git a/rtc/config.json b/rtc/config.json
new file mode 100644
index 00000000..9e26dfee
--- /dev/null
+++ b/rtc/config.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/rtc/package-lock.json b/rtc/package-lock.json
new file mode 100644
index 00000000..4977468d
--- /dev/null
+++ b/rtc/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/rtc/package.json b/rtc/package.json
new file mode 100644
index 00000000..0478fe69
--- /dev/null
+++ b/rtc/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/rtc/src/index.ts b/rtc/src/index.ts
new file mode 100644
index 00000000..3565fb6b
--- /dev/null
+++ b/rtc/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/rtc/src/main.cpp b/rtc/src/main.cpp
new file mode 100644
index 00000000..372eaa00
--- /dev/null
+++ b/rtc/src/main.cpp
@@ -0,0 +1,47 @@
+// $$$$$$\ $$\
+// $$ __$$\ $$ |
+// $$ / \__|$$$$$$\ $$$$$$$\ $$$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$ |
+// $$$$\ $$ __$$\ $$ _____|$$ _____|$$ _____|$$ __$$\ $$ __$$\ $$ __$$ |
+// $$ _| $$ / $$ |\$$$$$$\ \$$$$$$\ $$ / $$ / $$ |$$ | \__|$$ / $$ |
+// $$ | $$ | $$ | \____$$\ \____$$\ $$ | $$ | $$ |$$ | $$ | $$ |
+// $$ | \$$$$$$ |$$$$$$$ |$$$$$$$ |\$$$$$$$\ \$$$$$$ |$$ | \$$$$$$$ |
+// \__| \______/ \_______/ \_______/ \_______| \______/ \__| \_______|
+//
+//
+//
+// $$\ $$$$$$\
+// \__| $$ __$$\
+// $$\ $$\ $$$$$$\ $$\ $$$$$$$\ $$$$$$\ $$ / \__| $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\
+// \$$\ $$ |$$ __$$\ $$ |$$ _____|$$ __$$\ \$$$$$$\ $$ __$$\ $$ __$$\\$$\ $$ |$$ __$$\ $$ __$$\
+// \$$\$$ / $$ / $$ |$$ |$$ / $$$$$$$$ | \____$$\ $$$$$$$$ |$$ | \__|\$$\$$ / $$$$$$$$ |$$ | \__|
+// \$$$ / $$ | $$ |$$ |$$ | $$ ____|$$\ $$ |$$ ____|$$ | \$$$ / $$ ____|$$ |
+// \$ / \$$$$$$ |$$ |\$$$$$$$\ \$$$$$$$\ \$$$$$$ |\$$$$$$$\ $$ | \$ / \$$$$$$$\ $$ |
+// \_/ \______/ \__| \_______| \_______| \______/ \_______|\__| \_/ \_______|\__|
+//
+//
+//
+
+#include "rtcPeerHandler.hpp" //Handle peer connection requests
+#include "mongoStub.hpp" //Handle communication with the MongoDB server
+
+int main(int argc, char **argv){
+
+ auto commsHandler = std::make_shared<rtcPeerHandler>();
+ auto mongoHandler = std::make_unique<mongoStub>();
+
+ mongocxx::options::change_stream options;
+ //voiceEvents collection watcher
+ mongocxx::change_stream colCs = mongoHandler->getCol().watch(options);
+
+ std::cout << "Server created and listening for events" << std::endl;
+
+ //Check for new messages in the collection
+ for (;;){
+ std::vector<mongoStub::mongoMessage> t = mongoHandler->getNewMessages(&colCs);
+ for(auto &i : t){
+ std::cout << "[" << i.eventName << "] " << std::endl;
+ }
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/rtc/src/models/Activity.ts b/rtc/src/models/Activity.ts
new file mode 100644
index 00000000..17abd1ca
--- /dev/null
+++ b/rtc/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/rtc/src/models/Application.ts b/rtc/src/models/Application.ts
new file mode 100644
index 00000000..fae6e8db
--- /dev/null
+++ b/rtc/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/rtc/src/models/AuditLog.ts b/rtc/src/models/AuditLog.ts
new file mode 100644
index 00000000..02b2c444
--- /dev/null
+++ b/rtc/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/rtc/src/models/Ban.ts b/rtc/src/models/Ban.ts
new file mode 100644
index 00000000..f09950ee
--- /dev/null
+++ b/rtc/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/rtc/src/models/Channel.ts b/rtc/src/models/Channel.ts
new file mode 100644
index 00000000..1dd05896
--- /dev/null
+++ b/rtc/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/rtc/src/models/Emoji.ts b/rtc/src/models/Emoji.ts
new file mode 100644
index 00000000..3e5cad53
--- /dev/null
+++ b/rtc/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/rtc/src/models/Event.ts b/rtc/src/models/Event.ts
new file mode 100644
index 00000000..1564107d
--- /dev/null
+++ b/rtc/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/rtc/src/models/Guild.ts b/rtc/src/models/Guild.ts
new file mode 100644
index 00000000..13a7d078
--- /dev/null
+++ b/rtc/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/rtc/src/models/Interaction.ts b/rtc/src/models/Interaction.ts
new file mode 100644
index 00000000..764247a5
--- /dev/null
+++ b/rtc/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/rtc/src/models/Invite.ts b/rtc/src/models/Invite.ts
new file mode 100644
index 00000000..01f12003
--- /dev/null
+++ b/rtc/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/rtc/src/models/Member.ts b/rtc/src/models/Member.ts
new file mode 100644
index 00000000..d1c9ad9b
--- /dev/null
+++ b/rtc/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/rtc/src/models/Message.ts b/rtc/src/models/Message.ts
new file mode 100644
index 00000000..15a6f40d
--- /dev/null
+++ b/rtc/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/rtc/src/models/RateLimit.ts b/rtc/src/models/RateLimit.ts
new file mode 100644
index 00000000..6a0e1ffd
--- /dev/null
+++ b/rtc/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/rtc/src/models/ReadState.ts b/rtc/src/models/ReadState.ts
new file mode 100644
index 00000000..9c4fb323
--- /dev/null
+++ b/rtc/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/rtc/src/models/Role.ts b/rtc/src/models/Role.ts
new file mode 100644
index 00000000..c1111c84
--- /dev/null
+++ b/rtc/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/rtc/src/models/Status.ts b/rtc/src/models/Status.ts
new file mode 100644
index 00000000..5a9bf2ca
--- /dev/null
+++ b/rtc/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/rtc/src/models/Team.ts b/rtc/src/models/Team.ts
new file mode 100644
index 00000000..795c82d2
--- /dev/null
+++ b/rtc/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/rtc/src/models/Template.ts b/rtc/src/models/Template.ts
new file mode 100644
index 00000000..ad0f9104
--- /dev/null
+++ b/rtc/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/rtc/src/models/User.ts b/rtc/src/models/User.ts
new file mode 100644
index 00000000..c667e954
--- /dev/null
+++ b/rtc/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/rtc/src/models/VoiceState.ts b/rtc/src/models/VoiceState.ts
new file mode 100644
index 00000000..c1f90edd
--- /dev/null
+++ b/rtc/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/rtc/src/models/Webhook.ts b/rtc/src/models/Webhook.ts
new file mode 100644
index 00000000..7379e98f
--- /dev/null
+++ b/rtc/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/rtc/src/models/index.ts b/rtc/src/models/index.ts
new file mode 100644
index 00000000..d0a46bf9
--- /dev/null
+++ b/rtc/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/rtc/src/mongoStub.cpp b/rtc/src/mongoStub.cpp
new file mode 100644
index 00000000..ccd2abda
--- /dev/null
+++ b/rtc/src/mongoStub.cpp
@@ -0,0 +1,84 @@
+#include "mongoStub.hpp"
+
+mongoStub::mongoStub() {
+ if (this->client) {
+ this->db = client["fosscord"];
+
+ if (this->db) {
+ this->col = db["events"];
+
+ } else {
+ std::cout << "db not found";
+ exit(-1);
+ }
+ } else {
+ std::cout << "Client couldn't be initialized";
+ exit(-1);
+ }
+}
+
+// Too slow for my liking
+std::vector<mongoStub::mongoMessage> mongoStub::getNewMessages(
+ mongocxx::change_stream* colCs) {
+ std::vector<mongoStub::mongoMessage> retVec;
+
+ for (auto&& event : *colCs) {
+ mongoStub::mongoMessage returnValue;
+
+ std::cout << bsoncxx::to_json(event) << std::endl;
+
+ // Only listen to insert events (to avoid "precondition failed: data"
+ // exception)
+ if (event["operationType"].get_utf8().value.to_string() != "insert") {
+ continue;
+ }
+
+ std::string evName = event["fullDocument"]["event"].get_utf8().value.to_string();
+
+ if(evName.substr(0, 7)=="VSERVER"){ continue; } //Ignore the event if it's been emited by a voice server
+
+ if (evName == "UDP_CONNECTION") {
+ handleUdpRequest(
+ event["fullDocument"]["data"]["d"]["address"].get_utf8().value.to_string(),
+ event["fullDocument"]["data"]["d"]["port"].get_int32().value,
+ event["fullDocument"]["data"]["d"]["mode"].get_utf8().value.to_string()
+ );
+
+ } else if (evName == "VOICE_REQUEST") {
+ //TODO
+ continue;
+ }
+
+ returnValue.eventName = evName;
+ retVec.push_back(returnValue);
+ }
+
+ return retVec;
+}
+
+
+void mongoStub::handleUdpRequest(std::string address, int port, std::string mode) {
+ using bsoncxx::builder::basic::kvp;
+ using bsoncxx::builder::basic::sub_array;
+ using bsoncxx::builder::basic::sub_document;
+
+ auto builder = bsoncxx::builder::basic::document{};
+
+ //Handle UDP socket stuff (later tho)
+
+ builder.append(kvp("event", "VSERVER_UDP_RESPONSE"));
+ builder.append(kvp("op", "4"));
+ builder.append(kvp("d", [](sub_document subdoc) {
+ subdoc.append(kvp("mode", "CRYPT_MODE")),
+ subdoc.append(kvp("secret_key", [](sub_array subarr) {
+ subarr.append(1, 2, 3, 5); // HOW DO I GEN A SKEY?
+ }));
+ }));
+
+
+ bsoncxx::stdx::optional<mongocxx::result::insert_one> r= col.insert_one(builder.view());
+}
+
+void mongoStub::handleVoiceRequest() {
+ //Is this really needed? idk
+}
\ No newline at end of file
diff --git a/rtc/src/mongoStub.hpp b/rtc/src/mongoStub.hpp
new file mode 100644
index 00000000..2809142f
--- /dev/null
+++ b/rtc/src/mongoStub.hpp
@@ -0,0 +1,41 @@
+#ifndef MONGOSTUB_HPP
+#define MONGOSTUB_HPP
+
+#include <boost/utility.hpp>
+#include <cstdint>
+#include <iostream>
+#include <vector>
+#include <mongocxx/client.hpp>
+#include <mongocxx/instance.hpp>
+#include <mongocxx/change_stream.hpp>
+#include <bsoncxx/json.hpp>
+#include <bsoncxx/document/element.hpp>
+
+
+class mongoStub{
+ public:
+ mongoStub();
+
+ struct mongoMessage{
+ std::string eventName;
+ std::vector<std::string> data;
+ };
+
+ std::vector<mongoMessage> getNewMessages(mongocxx::change_stream* colCs);
+
+ mongocxx::collection getCol() const { return col; }
+
+
+
+ private:
+ mongocxx::instance instance;
+ mongocxx::client client{mongocxx::uri{}};
+ mongocxx::database db;
+ mongocxx::collection col;
+ mongocxx::change_stream* colCs = nullptr;
+
+ void handleUdpRequest(std::string address, int port, std::string mode);
+ void handleVoiceRequest();
+};
+
+#endif
diff --git a/rtc/src/rtcPeerHandler.cpp b/rtc/src/rtcPeerHandler.cpp
new file mode 100644
index 00000000..9bfc6466
--- /dev/null
+++ b/rtc/src/rtcPeerHandler.cpp
@@ -0,0 +1,83 @@
+#include "rtcPeerHandler.hpp"
+
+rtcPeerHandler::rtcPeerHandler() {
+ rtc::InitLogger(rtc::LogLevel::Verbose, NULL);
+}
+
+void rtcPeerHandler::initiateConnection(std::string peerIP, int peerPort) {
+ // Socket connection between client and server
+ SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
+ sockaddr_in addr;
+ addr.sin_addr.s_addr = inet_addr(peerIP.c_str());
+ addr.sin_port = htons(peerPort);
+ addr.sin_family = AF_INET;
+
+ rtc::Configuration conf;
+ conf.enableIceTcp = false;
+ conf.disableAutoNegotiation = false;
+
+ auto pc = std::make_shared<rtc::PeerConnection>(conf);
+
+ rtc::Description::Audio media("audio",
+ rtc::Description::Direction::SendRecv);
+ media.addOpusCodec(96);
+ media.setBitrate(64);
+
+ auto track = pc->addTrack(media);
+
+ // auto session = std::make_shared<rtc::MediaHandler>();
+
+ // track->setMediaHandler(session);
+
+ rtc::Reliability rtcRel;
+ rtcRel.unordered = true;
+ rtcRel.type = rtc::Reliability::Type::Timed;
+ rtcRel.rexmit = 500;
+
+ rtc::DataChannelInit rtcConf;
+ rtcConf.reliability = rtcRel;
+ rtcConf.negotiated = false;
+
+ pc->onStateChange([](rtc::PeerConnection::State state) {
+ std::cout << "State: " << state << std::endl;
+ if (state == rtc::PeerConnection::State::Disconnected ||
+ state == rtc::PeerConnection::State::Failed ||
+ state == rtc::PeerConnection::State::Closed) {
+ // remove disconnected client
+ }
+ });
+
+ pc->onGatheringStateChange([](rtc::PeerConnection::GatheringState state) {
+ std::cout << "Gathering State: " << state << std::endl;
+ });
+
+ /*std::tuple<rtc::Track*, rtc::RtcpSrReporter*> addAudio(
+
+ const std::shared_ptr<rtc::PeerConnection> pc,
+ const uint8_t payloadType, const uint32_t ssrc, const std::string cname,
+ const std::string msid, const std::function<void(void)> onOpen) {
+ auto audio = Description::Audio(cname);
+ audio.addOpusCodec(payloadType);
+ audio.addSSRC(ssrc, cname, msid, cname);
+ auto track = pc->addTrack(audio);
+ // create RTP configuration
+ auto rtpConfig = make_shared<RtpPacketizationConfig>(
+ ssrc, cname, payloadType, OpusRtpPacketizer::defaultClockRate);
+ // create packetizer
+ auto packetizer = make_shared<OpusRtpPacketizer>(rtpConfig);
+ // create opus handler
+ auto opusHandler = make_shared<OpusPacketizationHandler>(packetizer);
+
+ // add RTCP SR handler
+ auto srReporter = make_shared<RtcpSrReporter>(rtpConfig);
+ opusHandler->addToChain(srReporter);
+
+ // set handler
+ track->setMediaHandler(opusHandler);
+ track->onOpen(onOpen);
+ auto trackData = make_shared<ClientTrackData>(track, srReporter);
+ return trackData;
+ }*/
+
+ pc->createDataChannel("Fosscord voice connection", rtcConf);
+}
diff --git a/rtc/src/rtcPeerHandler.hpp b/rtc/src/rtcPeerHandler.hpp
new file mode 100644
index 00000000..3ba32a83
--- /dev/null
+++ b/rtc/src/rtcPeerHandler.hpp
@@ -0,0 +1,32 @@
+#include "libdatachannel/rtc.hpp"
+#include <iostream>
+#include <memory>
+#include "nlohmann/json.hpp"
+#include <array>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+typedef int SOCKET;
+#endif
+
+using json = nlohmann::json;
+
+#ifndef RTCPEERHANDLER
+#define RTCPEERHANDLER
+class rtcPeerHandler{
+public:
+ rtcPeerHandler();
+ void initiateConnection(std::string peerIP, int peerPort);
+
+ struct client
+ {
+ std::shared_ptr<rtc::PeerConnection> pc;
+ std::shared_ptr<rtc::DataChannel> dc;
+ };
+
+private:
+ std::map<SOCKET, client> clients;
+};
+#endif
\ No newline at end of file
diff --git a/rtc/src/rtcServer.hpp b/rtc/src/rtcServer.hpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/rtc/src/rtcServer.hpp
diff --git a/rtc/src/util/BitField.ts b/rtc/src/util/BitField.ts
new file mode 100644
index 00000000..728dc632
--- /dev/null
+++ b/rtc/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/rtc/src/util/Config.ts b/rtc/src/util/Config.ts
new file mode 100644
index 00000000..78b44315
--- /dev/null
+++ b/rtc/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/rtc/src/util/Constants.ts b/rtc/src/util/Constants.ts
new file mode 100644
index 00000000..a9978c51
--- /dev/null
+++ b/rtc/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/rtc/src/util/Database.ts b/rtc/src/util/Database.ts
new file mode 100644
index 00000000..8c6847a8
--- /dev/null
+++ b/rtc/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/rtc/src/util/Intents.ts b/rtc/src/util/Intents.ts
new file mode 100644
index 00000000..943b29cf
--- /dev/null
+++ b/rtc/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/rtc/src/util/MessageFlags.ts b/rtc/src/util/MessageFlags.ts
new file mode 100644
index 00000000..c76be4c8
--- /dev/null
+++ b/rtc/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/rtc/src/util/MongoBigInt.ts b/rtc/src/util/MongoBigInt.ts
new file mode 100644
index 00000000..fc451925
--- /dev/null
+++ b/rtc/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/rtc/src/util/Permissions.ts b/rtc/src/util/Permissions.ts
new file mode 100644
index 00000000..445e901f
--- /dev/null
+++ b/rtc/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/rtc/src/util/RabbitMQ.ts b/rtc/src/util/RabbitMQ.ts
new file mode 100644
index 00000000..9da41990
--- /dev/null
+++ b/rtc/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/rtc/src/util/Regex.ts b/rtc/src/util/Regex.ts
new file mode 100644
index 00000000..bbd48bca
--- /dev/null
+++ b/rtc/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/rtc/src/util/Snowflake.ts b/rtc/src/util/Snowflake.ts
new file mode 100644
index 00000000..1d725710
--- /dev/null
+++ b/rtc/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/rtc/src/util/String.ts b/rtc/src/util/String.ts
new file mode 100644
index 00000000..55f11e8d
--- /dev/null
+++ b/rtc/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/rtc/src/util/UserFlags.ts b/rtc/src/util/UserFlags.ts
new file mode 100644
index 00000000..72394eff
--- /dev/null
+++ b/rtc/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/rtc/src/util/checkToken.ts b/rtc/src/util/checkToken.ts
new file mode 100644
index 00000000..91bf08d5
--- /dev/null
+++ b/rtc/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/rtc/src/util/index.ts b/rtc/src/util/index.ts
new file mode 100644
index 00000000..7523a6ad
--- /dev/null
+++ b/rtc/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/rtc/src/util/toBigInt.ts b/rtc/src/util/toBigInt.ts
new file mode 100644
index 00000000..d57c4568
--- /dev/null
+++ b/rtc/src/util/toBigInt.ts
@@ -0,0 +1,3 @@
+export default function toBigInt(string: String): BigInt {
+ return BigInt(string);
+}
diff --git a/rtc/tsconfig.json b/rtc/tsconfig.json
new file mode 100644
index 00000000..520774d3
--- /dev/null
+++ b/rtc/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. */
+ }
+}
|